// This test verifies that ContainerID is properly set in the // ContainerStatus returned from 'status()' method. TEST_F(MesosContainerizerTest, StatusWithContainerID) { slave::Flags flags = CreateSlaveFlags(); flags.launcher = "posix"; flags.isolation = "posix/cpu"; Fetcher fetcher(flags); Try<MesosContainerizer*> create = MesosContainerizer::create( flags, true, &fetcher); ASSERT_SOME(create); Owned<MesosContainerizer> containerizer(create.get()); SlaveState state; state.id = SlaveID(); AWAIT_READY(containerizer->recover(state)); ContainerID containerId; containerId.set_value(id::UUID::random().toString()); Try<string> directory = environment->mkdtemp(); ASSERT_SOME(directory); Future<Containerizer::LaunchResult> launch = containerizer->launch( containerId, createContainerConfig( None(), createExecutorInfo("executor", "sleep 1000", "cpus:1"), directory.get()), map<string, string>(), None()); AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch); Future<ContainerStatus> status = containerizer->status(containerId); AWAIT_READY(status); EXPECT_EQ(containerId, status->container_id()); Future<Option<ContainerTermination>> wait = containerizer->wait(containerId); containerizer->destroy(containerId); AWAIT_READY(wait); ASSERT_SOME(wait.get()); ASSERT_TRUE(wait.get()->has_status()); EXPECT_WTERMSIG_EQ(SIGKILL, wait.get()->status()); }
// This test verifies that executor API and operator API calls receive an // unsuccessful response if the request contains a properly-signed // authentication token with invalid claims. TEST_F(ExecutorAuthorizationTest, FailedApiCalls) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Start an agent with permissive ACLs so that a task can be launched and the // local authorizer's implicit executor authorization will be performed. ACLs acls; acls.set_permissive(true); slave::Flags flags = CreateSlaveFlags(); flags.acls = acls; Owned<MasterDetector> detector = master.get()->createDetector(); v1::Resources resources = v1::Resources::parse("cpus:0.1;mem:32;disk:32").get(); v1::ExecutorInfo executorInfo; executorInfo.set_type(v1::ExecutorInfo::DEFAULT); executorInfo.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID); executorInfo.mutable_resources()->CopyFrom(resources); auto executor = std::make_shared<v1::MockHTTPExecutor>(); Owned<TestContainerizer> containerizer(new TestContainerizer( devolve(executorInfo.executor_id()), executor)); Try<Owned<cluster::Slave>> slave = this->StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); auto scheduler = std::make_shared<v1::MockHTTPScheduler>(); Future<Nothing> connected; EXPECT_CALL(*scheduler, connected(_)) .WillOnce(FutureSatisfy(&connected)); v1::scheduler::TestMesos mesos( master.get()->pid, ContentType::PROTOBUF, scheduler); AWAIT_READY(connected); Future<v1::scheduler::Event::Subscribed> frameworkSubscribed; EXPECT_CALL(*scheduler, subscribed(_, _)) .WillOnce(FutureArg<1>(&frameworkSubscribed)); Future<v1::scheduler::Event::Offers> offers; EXPECT_CALL(*scheduler, offers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. EXPECT_CALL(*scheduler, heartbeat(_)) .WillRepeatedly(Return()); // Ignore heartbeats. mesos.send(v1::createCallSubscribe(v1::DEFAULT_FRAMEWORK_INFO)); AWAIT_READY(frameworkSubscribed); v1::FrameworkID frameworkId(frameworkSubscribed->framework_id()); executorInfo.mutable_framework_id()->CopyFrom(frameworkId); AWAIT_READY(offers); ASSERT_FALSE(offers->offers().empty()); Future<v1::executor::Mesos*> executorLib; EXPECT_CALL(*executor, connected(_)) .WillOnce(FutureArg<0>(&executorLib)); const v1::Offer& offer = offers->offers(0); const v1::AgentID& agentId = offer.agent_id(); { v1::scheduler::Call call; call.mutable_framework_id()->CopyFrom(frameworkId); call.set_type(v1::scheduler::Call::ACCEPT); v1::scheduler::Call::Accept* accept = call.mutable_accept(); accept->add_offer_ids()->CopyFrom(offer.id()); v1::Offer::Operation* operation = accept->add_operations(); operation->set_type(v1::Offer::Operation::LAUNCH_GROUP); v1::TaskInfo taskInfo = v1::createTask(agentId, resources, SLEEP_COMMAND(1000)); v1::TaskGroupInfo taskGroup; taskGroup.add_tasks()->CopyFrom(taskInfo); v1::Offer::Operation::LaunchGroup* launchGroup = operation->mutable_launch_group(); launchGroup->mutable_executor()->CopyFrom(executorInfo); launchGroup->mutable_task_group()->CopyFrom(taskGroup); mesos.send(call); } AWAIT_READY(executorLib); Future<v1::executor::Event::Subscribed> executorSubscribed; EXPECT_CALL(*executor, subscribed(_, _)) .WillOnce(FutureArg<1>(&executorSubscribed)); Future<Nothing> launchGroup; EXPECT_CALL(*executor, launchGroup(_, _)) .WillOnce(FutureSatisfy(&launchGroup)); { v1::executor::Call call; call.mutable_framework_id()->CopyFrom(frameworkId); call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID); call.set_type(v1::executor::Call::SUBSCRIBE); call.mutable_subscribe(); executorLib.get()->send(call); } // Wait for the executor to subscribe. Once it is in the SUBSCRIBED state, // the UPDATE and MESSAGE executor calls can be attempted. AWAIT_READY(executorSubscribed); AWAIT_READY(launchGroup); // Create a principal which contains an incorrect ContainerID. hashmap<string, string> claims; claims["fid"] = frameworkId.value(); claims["eid"] = v1::DEFAULT_EXECUTOR_ID.value(); claims["cid"] = id::UUID::random().toString(); Principal incorrectPrincipal(None(), claims); // Generate an authentication token which is signed using the correct key, // but contains an invalid set of claims. Owned<JWTSecretGenerator> jwtSecretGenerator( new JWTSecretGenerator(DEFAULT_JWT_SECRET_KEY)); Future<Secret> authenticationToken = jwtSecretGenerator->generate(incorrectPrincipal); AWAIT_READY(authenticationToken); v1::ContainerID containerId; containerId.set_value(id::UUID::random().toString()); containerId.mutable_parent()->CopyFrom(executorSubscribed->container_id()); http::Headers headers; headers["Authorization"] = "Bearer " + authenticationToken->value().data(); // Since the executor library has already been initialized with a valid // authentication token, we use an HTTP helper function to send the // executor API and operator API calls with an invalid token. { v1::agent::Call call; call.set_type(v1::agent::Call::LAUNCH_NESTED_CONTAINER); call.mutable_launch_nested_container()->mutable_container_id() ->CopyFrom(containerId); Future<http::Response> response = http::post( slave.get()->pid, "api/v1", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); } { v1::agent::Call call; call.set_type(v1::agent::Call::LAUNCH_NESTED_CONTAINER_SESSION); call.mutable_launch_nested_container_session()->mutable_container_id() ->CopyFrom(containerId); call.mutable_launch_nested_container_session()->mutable_command() ->set_value("sleep 120"); Future<http::Response> response = http::post( slave.get()->pid, "api/v1", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); } { v1::agent::Call call; call.set_type(v1::agent::Call::WAIT_NESTED_CONTAINER); call.mutable_wait_nested_container()->mutable_container_id() ->CopyFrom(containerId); Future<http::Response> response = http::post( slave.get()->pid, "api/v1", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); } { v1::agent::Call call; call.set_type(v1::agent::Call::KILL_NESTED_CONTAINER); call.mutable_kill_nested_container()->mutable_container_id() ->CopyFrom(containerId); Future<http::Response> response = http::post( slave.get()->pid, "api/v1", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); } { v1::agent::Call call; call.set_type(v1::agent::Call::REMOVE_NESTED_CONTAINER); call.mutable_remove_nested_container()->mutable_container_id() ->CopyFrom(containerId); Future<http::Response> response = http::post( slave.get()->pid, "api/v1", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); } { v1::agent::Call call; call.set_type(v1::agent::Call::ATTACH_CONTAINER_OUTPUT); call.mutable_attach_container_output()->mutable_container_id() ->CopyFrom(containerId); Future<http::Response> response = http::post( slave.get()->pid, "api/v1", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); } const string failureMessage = "does not contain a 'cid' claim with the correct active ContainerID"; { v1::TaskStatus status; status.mutable_task_id()->set_value(id::UUID::random().toString()); status.set_state(v1::TASK_RUNNING); status.set_uuid(id::UUID::random().toBytes()); status.set_source(v1::TaskStatus::SOURCE_EXECUTOR); v1::executor::Call call; call.set_type(v1::executor::Call::UPDATE); call.mutable_framework_id()->CopyFrom(frameworkId); call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID); call.mutable_update()->mutable_status()->CopyFrom(status); Future<http::Response> response = http::post( slave.get()->pid, "api/v1/executor", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); EXPECT_TRUE(strings::contains(response->body, failureMessage)); } { v1::executor::Call call; call.set_type(v1::executor::Call::MESSAGE); call.mutable_framework_id()->CopyFrom(frameworkId); call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID); call.mutable_message()->set_data("executor message"); Future<http::Response> response = http::post( slave.get()->pid, "api/v1/executor", headers, serialize(ContentType::PROTOBUF, call), stringify(ContentType::PROTOBUF)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response); EXPECT_TRUE(strings::contains(response->body, failureMessage)); } EXPECT_CALL(*executor, shutdown(_)) .Times(AtMost(1)); }