TYPED_TEST(AuthorizationTest, PrincipalNotOfferedAnyRoleRestrictive) { // A principal "foo" can be offered "analytics" role's resources. ACLs acls; acls.set_permissive(false); mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values("foo"); acl->mutable_roles()->add_values("analytics"); // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); Try<Nothing> initialized = authorizer.get()->initialize(acls); ASSERT_SOME(initialized); // Principal "foo" can be offered "analytics" role's resources. mesos::ACL::RegisterFramework request; request.mutable_principals()->add_values("foo"); request.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request)); // Principal "bar" cannot be offered "analytics" role's resources. mesos::ACL::RegisterFramework request2; request2.mutable_principals()->add_values("bar"); request2.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request2)); // Principal "bar" cannot be offered "ads" role's resources because no ACL. mesos::ACL::RegisterFramework request3; request3.mutable_principals()->add_values("bar"); request3.mutable_roles()->add_values("ads"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request3)); }
TEST_F(AuthorizationTest, PrincipalNotOfferedAnyRoleRestrictive) { // A principal "foo" can be offered "analytics" role's resources. ACLs acls; acls.set_permissive(false); mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values("foo"); acl->mutable_roles()->add_values("analytics"); // Create an Authorizer with the ACLs. Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); ASSERT_SOME(authorizer); // Principal "foo" can be offered "analytics" role's resources. mesos::ACL::RegisterFramework request; request.mutable_principals()->add_values("foo"); request.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "bar" cannot be offered "analytics" role's resources. mesos::ACL::RegisterFramework request2; request2.mutable_principals()->add_values("bar"); request2.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); // Principal "bar" cannot be offered "ads" role's resources because no ACL. mesos::ACL::RegisterFramework request3; request3.mutable_principals()->add_values("bar"); request3.mutable_roles()->add_values("ads"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request3)); }
TYPED_TEST(AuthorizationTest, PrincipalRunAsSomeUserRestrictive) { // A principal can run as "user1"; ACLs acls; acls.set_permissive(false); // Restrictive. mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_users()->add_values("user1"); // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); Try<Nothing> initialized = authorizer.get()->initialize(acls); ASSERT_SOME(initialized); // Principal "foo" can run as "user1". mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_users()->add_values("user1"); AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request)); // Principal "foo" cannot run as "user2". mesos::ACL::RunTask request2; request2.mutable_principals()->add_values("foo"); request2.mutable_users()->add_values("user2"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request2)); // Principal "bar" cannot run as "user2" since no ACL is set. mesos::ACL::RunTask request3; request3.mutable_principals()->add_values("bar"); request3.mutable_users()->add_values("user2"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request3)); }
TEST_F(AuthorizationTest, PrincipalRunAsSomeUserRestrictive) { // A principal can run as "user1"; ACLs acls; acls.set_permissive(false); // Restrictive. mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_users()->add_values("user1"); // Create an Authorizer with the ACLs. Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); ASSERT_SOME(authorizer); // Principal "foo" can run as "user1". mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_users()->add_values("user1"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "foo" cannot run as "user2". mesos::ACL::RunTask request2; request2.mutable_principals()->add_values("foo"); request2.mutable_users()->add_values("user2"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); // Principal "bar" cannot run as "user2" since no ACL is set. mesos::ACL::RunTask request3; request3.mutable_principals()->add_values("bar"); request3.mutable_users()->add_values("user2"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request3)); }
TYPED_TEST(AuthorizationTest, PrincipalNotOfferedAnyRoleRestrictive) { ACLs acls; acls.set_permissive(false); { // A principal "foo" can be offered "analytics" role's resources. mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values("foo"); acl->mutable_roles()->add_values("analytics"); } // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(parameterize(acls)); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); // Principal "foo" can be offered "analytics" role's resources. { authorization::Request request; request.set_action(authorization::REGISTER_FRAMEWORK_WITH_ROLE); request.mutable_subject()->set_value("foo"); request.mutable_object()->set_value("analytics"); AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request)); } // Principal "bar" cannot be offered "analytics" role's resources. { authorization::Request request; request.set_action(authorization::REGISTER_FRAMEWORK_WITH_ROLE); request.mutable_subject()->set_value("bar"); request.mutable_object()->set_value("analytics"); AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request)); } // Principal "bar" cannot be offered "ads" role's resources because no ACL. { authorization::Request request; request.set_action(authorization::REGISTER_FRAMEWORK_WITH_ROLE); request.mutable_subject()->set_value("bar"); request.mutable_object()->set_value("ads"); AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request)); } }
TYPED_TEST(AuthorizationTest, PrincipalRunAsSomeUserRestrictive) { ACLs acls; acls.set_permissive(false); // Restrictive. { // A principal can run as "user1"; mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_users()->add_values("user1"); } // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(parameterize(acls)); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); // Principal "foo" can run as "user1". { authorization::Request request; request.set_action(authorization::RUN_TASK_WITH_USER); request.mutable_subject()->set_value("foo"); request.mutable_object()->set_value("user1"); AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request)); } // Principal "foo" cannot run as "user2". { authorization::Request request; request.set_action(authorization::RUN_TASK_WITH_USER); request.mutable_subject()->set_value("foo"); request.mutable_object()->set_value("user2"); AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request)); } // Principal "bar" cannot run as "user2" since no ACL is set. { authorization::Request request; request.set_action(authorization::RUN_TASK_WITH_USER); request.mutable_subject()->set_value("bar"); request.mutable_object()->set_value("user2"); AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request)); } }
// This test verifies that only authorized principals // can access the '/flags' endpoint. TYPED_TEST(SlaveAuthorizerTest, AuthorizeFlagsEndpoint) { const string endpoint = "flags"; // Setup ACLs so that only the default principal // can access the '/flags' endpoint. ACLs acls; acls.set_permissive(false); mesos::ACL::GetEndpoint* acl = acls.add_get_endpoints(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_paths()->add_values("/" + endpoint); // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(parameterize(acls)); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); StandaloneMasterDetector detector; Try<Owned<cluster::Slave>> agent = this->StartSlave(&detector, authorizer.get()); ASSERT_SOME(agent); Future<Response> response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body; response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL_2)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response) << response.get().body; }
// This test verifies that access to the '/flags' endpoint can be authorized // without authentication if an authorization rule exists that applies to // anyone. The authorizer will map the absence of a principal to "ANY". TYPED_TEST(SlaveAuthorizerTest, AuthorizeFlagsEndpointWithoutPrincipal) { const string endpoint = "flags"; // Because the authenticators' lifetime is tied to libprocess's lifetime, // it may already be set by other tests. We have to unset it here to disable // HTTP authentication. // TODO(nfnt): Fix this behavior. The authenticator should be unset by // every test case that sets it, similar to how it's done for the master. http::authentication::unsetAuthenticator( slave::DEFAULT_HTTP_AUTHENTICATION_REALM); // Setup ACLs so that any principal can access the '/flags' endpoint. ACLs acls; acls.set_permissive(false); mesos::ACL::GetEndpoint* acl = acls.add_get_endpoints(); acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY); acl->mutable_paths()->add_values("/" + endpoint); slave::Flags agentFlags = this->CreateSlaveFlags(); agentFlags.acls = acls; agentFlags.authenticate_http = false; agentFlags.http_credentials = None(); // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(parameterize(acls)); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); StandaloneMasterDetector detector; Try<Owned<cluster::Slave>> agent = this->StartSlave( &detector, authorizer.get(), agentFlags); ASSERT_SOME(agent); Future<Response> response = http::get(agent.get()->pid, endpoint); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body; }
void execute(const string& script) { // Create a temporary directory for the test. Try<string> directory = environment->mkdtemp(); CHECK_SOME(directory) << "Failed to create temporary directory"; if (flags.verbose) { std::cerr << "Using temporary directory '" << directory.get() << "'" << std::endl; } // Determine the path for the script. Result<string> path = os::realpath(path::join(flags.source_dir, "src", "tests", script)); if (!path.isSome()) { FAIL() << "Failed to locate script: " << (path.isError() ? path.error() : "No such file or directory"); } // Fork a process to change directory and run the test. pid_t pid; if ((pid = fork()) == -1) { FAIL() << "Failed to fork to launch script"; } if (pid > 0) { // In parent process. int status; while (wait(&status) != pid || WIFSTOPPED(status)); CHECK(WIFEXITED(status) || WIFSIGNALED(status)); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { FAIL() << script << " " << WSTRINGIFY(status); } } else { // In child process. DO NOT USE GLOG! // Start by cd'ing into the temporary directory. Try<Nothing> chdir = os::chdir(directory.get()); if (chdir.isError()) { std::cerr << "Failed to chdir to '" << directory.get() << "': " << chdir.error() << std::endl; abort(); } // Redirect output to /dev/null unless the test is verbose. if (!flags.verbose) { if (freopen("/dev/null", "w", stdout) == NULL || freopen("/dev/null", "w", stderr) == NULL) { std::cerr << "Failed to redirect stdout/stderr to /dev/null:" << os::strerror(errno) << std::endl; abort(); } } // Set up the environment for executing the script. os::setenv("MESOS_SOURCE_DIR", flags.source_dir); os::setenv("MESOS_BUILD_DIR", flags.build_dir); os::setenv("MESOS_WEBUI_DIR", path::join(flags.source_dir, "src", "webui")); os::setenv("MESOS_LAUNCHER_DIR", path::join(flags.build_dir, "src")); // Enable replicated log based registry. os::setenv("MESOS_REGISTRY", "replicated_log"); // Enable authentication. os::setenv("MESOS_AUTHENTICATE", "true"); // Create test credentials. const string& credentials = DEFAULT_CREDENTIAL.principal() + " " + DEFAULT_CREDENTIAL.secret(); const string& credentialsPath = path::join(directory.get(), "credentials"); CHECK_SOME(os::write(credentialsPath, credentials)) << "Failed to write credentials to '" << credentialsPath << "'"; os::setenv("MESOS_CREDENTIALS", "file://" + credentialsPath); // We set test credentials here for example frameworks to use. os::setenv("DEFAULT_PRINCIPAL", DEFAULT_CREDENTIAL.principal()); os::setenv("DEFAULT_SECRET", DEFAULT_CREDENTIAL.secret()); // TODO(bmahler): Update the example frameworks to use flags and // remove the special DEFAULT_* environment variables above. os::setenv("MESOS_PRINCIPAL", DEFAULT_CREDENTIAL.principal()); os::setenv("MESOS_SECRET", DEFAULT_CREDENTIAL.secret()); // Create test ACLs. ACLs acls; acls.set_permissive(false); mesos::ACL::RunTask* run = acls.add_run_tasks(); run->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); Result<string> user = os::user(); CHECK_SOME(user) << "Failed to get current user name"; run->mutable_users()->add_values(user.get()); mesos::ACL::RegisterFramework* register_ = acls.add_register_frameworks(); register_->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); register_->mutable_roles()->add_values("*"); const string& aclsPath = path::join(directory.get(), "acls"); CHECK_SOME(os::write(aclsPath, stringify(JSON::protobuf(acls)))) << "Failed to write ACLs to '" << aclsPath << "'"; os::setenv("MESOS_ACLS", "file://" + aclsPath); // Now execute the script. execl(path.get().c_str(), path.get().c_str(), (char*) NULL); std::cerr << "Failed to execute '" << script << "': " << os::strerror(errno) << std::endl; abort(); } }
// 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)); }
// This test verifies that default executor subscription fails if the executor // provides a properly-signed authentication token with invalid claims. TEST_F(ExecutorAuthorizationTest, FailedSubscribe) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Start an agent with permissive ACLs so that a task can be launched. ACLs acls; acls.set_permissive(true); Result<Authorizer*> authorizer = Authorizer::create(acls); ASSERT_SOME(authorizer); slave::Flags flags = CreateSlaveFlags(); flags.acls = acls; Owned<MasterDetector> detector = master.get()->createDetector(); auto executor = std::make_shared<v1::MockHTTPExecutor>(); 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); Owned<TestContainerizer> containerizer( new TestContainerizer(devolve(executorInfo.executor_id()), executor)); // This pointer is passed to the agent, which will perform the cleanup. Owned<MockSecretGenerator> mockSecretGenerator(new MockSecretGenerator()); Try<Owned<cluster::Slave>> slave = StartSlave( detector.get(), containerizer.get(), mockSecretGenerator.get(), authorizer.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> subscribed; EXPECT_CALL(*scheduler, subscribed(_, _)) .WillOnce(FutureArg<1>(&subscribed)); 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(subscribed); v1::FrameworkID frameworkId(subscribed->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)); Owned<JWTSecretGenerator> jwtSecretGenerator( new JWTSecretGenerator(DEFAULT_JWT_SECRET_KEY)); // 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 principal(None(), claims); // Generate an authentication token which is signed using the correct key, // but contains an invalid set of claims. Future<Secret> authenticationToken = jwtSecretGenerator->generate(principal); AWAIT_READY(authenticationToken); EXPECT_CALL(*mockSecretGenerator, generate(_)) .WillOnce(Return(authenticationToken.get())); const v1::Offer& offer = offers->offers(0); const v1::AgentID& agentId = offer.agent_id(); { v1::TaskInfo taskInfo = v1::createTask(agentId, resources, SLEEP_COMMAND(1000)); v1::TaskGroupInfo taskGroup; taskGroup.add_tasks()->CopyFrom(taskInfo); 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::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); { 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); } Future<v1::executor::Event::Error> error; EXPECT_CALL(*executor, error(_, _)) .WillOnce(FutureArg<1>(&error)); AWAIT_READY(error); EXPECT_EQ( error->message(), "Received unexpected '403 Forbidden' () for SUBSCRIBE"); }
// This test verifies that a task group is launched on the agent if the executor // provides a valid authentication token specifying its own ContainerID. TEST_F(ExecutorAuthorizationTest, RunTaskGroup) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Start an agent with permissive ACLs so that a task can be launched. ACLs acls; acls.set_permissive(true); slave::Flags flags = CreateSlaveFlags(); flags.acls = acls; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(frameworkId); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:0.5;mem:32").get(), "sleep 1000"); Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)); Resources executorResources = allocatedResources(Resources::parse("cpus:0.1;mem:32;disk:32").get(), "*"); ExecutorInfo executor; executor.mutable_executor_id()->set_value("default"); executor.set_type(ExecutorInfo::DEFAULT); executor.mutable_framework_id()->CopyFrom(frameworkId.get()); executor.mutable_resources()->CopyFrom(executorResources); TaskGroupInfo taskGroup; taskGroup.add_tasks()->CopyFrom(task); driver.acceptOffers({offer.id()}, {LAUNCH_GROUP(executor, taskGroup)}); AWAIT_READY(status); ASSERT_EQ(task.task_id(), status->task_id()); EXPECT_EQ(TASK_STARTING, status->state()); driver.stop(); driver.join(); }
// This test verifies that a task is launched on the agent if the task // user is authorized based on `run_tasks` ACL configured on the agent // to only allow whitelisted users to run tasks on the agent. TYPED_TEST(SlaveAuthorizerTest, AuthorizeRunTaskOnAgent) { // Get the current user. Result<string> user = os::user(); ASSERT_SOME(user) << "Failed to get the current user name" << (user.isError() ? ": " + user.error() : ""); Try<Owned<cluster::Master>> master = this->StartMaster(); ASSERT_SOME(master); // Start a slave with `bar` and the current user being the only authorized // users to launch tasks on the agent. ACLs acls; acls.set_permissive(false); // Restrictive. mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->set_type(ACL::Entity::ANY); acl->mutable_users()->add_values("bar"); acl->mutable_users()->add_values(user.get()); slave::Flags slaveFlags = this->CreateSlaveFlags(); slaveFlags.acls = acls; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = this->StartSlave( detector.get(), slaveFlags); ASSERT_SOME(slave); // Create a framework with user `foo`. FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_user("foo"); MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); // Framework is registered since the master admits frameworks of any user. AWAIT_READY(frameworkId); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; // Launch the first task with no user, so it defaults to the // framework user `foo`. TaskInfo task1 = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:32").get(), "sleep 1000"); // Launch the second task as the current user. TaskInfo task2 = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:32").get(), "sleep 1000"); task2.mutable_command()->set_user(user.get()); // The first task should fail since the task user `foo` is not an // authorized user that can launch a task. However, the second task // should succeed. Future<TaskStatus> status0; Future<TaskStatus> status1; Future<TaskStatus> status2; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status0)) .WillOnce(FutureArg<1>(&status1)) .WillOnce(FutureArg<1>(&status2)); driver.acceptOffers( {offer.id()}, {LAUNCH({task1, task2})}); // Wait for TASK_ERROR for 1st task, and TASK_STARTING followed by // TASK_RUNNING for 2nd task. AWAIT_READY(status0); AWAIT_READY(status1); AWAIT_READY(status2); // Validate both the statuses. Note that the order of receiving the // status updates for the 2 tasks is not deterministic, but we know // that task2's TASK_RUNNING arrives after TASK_STARTING. hashmap<TaskID, TaskStatus> statuses; statuses[status0->task_id()] = status0.get(); statuses[status1->task_id()] = status1.get(); statuses[status2->task_id()] = status2.get(); ASSERT_TRUE(statuses.contains(task1.task_id())); EXPECT_EQ(TASK_ERROR, statuses.at(task1.task_id()).state()); EXPECT_EQ(TaskStatus::SOURCE_SLAVE, statuses.at(task1.task_id()).source()); EXPECT_EQ(TaskStatus::REASON_TASK_UNAUTHORIZED, statuses.at(task1.task_id()).reason()); ASSERT_TRUE(statuses.contains(task2.task_id())); EXPECT_EQ(TASK_RUNNING, statuses.at(task2.task_id()).state()); driver.stop(); driver.join(); }