You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ti...@apache.org on 2017/06/16 19:31:04 UTC
[1/6] mesos git commit: Adds authorization for the master's v1 API
call SET_LOGGING_LEVEL.
Repository: mesos
Updated Branches:
refs/heads/master 0606514dd -> def0b106a
Adds authorization for the master's v1 API call SET_LOGGING_LEVEL.
Enables authorization in the v1 API call \`SET_LOGGING_LEVEL\` following
the existing implementation for the agent call with the same name.
Likewise it reuses the authorizer action \`SET_LOG_LEVEL\`.
Review: https://reviews.apache.org/r/58955/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/9ea7fae6
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/9ea7fae6
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/9ea7fae6
Branch: refs/heads/master
Commit: 9ea7fae68477c7cb8e33e04fd32616a44ee9be2b
Parents: 0606514
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 16 20:29:50 2017 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Jun 16 20:29:50 2017 +0200
----------------------------------------------------------------------
src/master/http.cpp | 30 ++++++++++++++++++++----
src/tests/api_tests.cpp | 56 ++++++++++++++++++++++++++++++--------------
2 files changed, 65 insertions(+), 21 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/9ea7fae6/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 1dcfe6e..857e82b 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -1998,10 +1998,32 @@ Future<Response> Master::Http::setLoggingLevel(
Duration duration =
Nanoseconds(call.set_logging_level().duration().nanoseconds());
- return dispatch(process::logging(), &Logging::set_level, level, duration)
- .then([]() -> Response {
- return OK();
- });
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::SET_LOG_LEVEL);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ return approver.then([level, duration](const Owned<ObjectApprover>& approver)
+ -> Future<Response> {
+ Try<bool> approved = approver->approved((ObjectApprover::Object()));
+
+ if (approved.isError()) {
+ return InternalServerError("Authorization error: " + approved.error());
+ } else if (!approved.get()) {
+ return Forbidden();
+ }
+
+ return dispatch(process::logging(), &Logging::set_level, level, duration)
+ .then([]() -> Response {
+ return OK();
+ });
+ });
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/9ea7fae6/src/tests/api_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/api_tests.cpp b/src/tests/api_tests.cpp
index 91b3473..e071700 100644
--- a/src/tests/api_tests.cpp
+++ b/src/tests/api_tests.cpp
@@ -735,7 +735,16 @@ TEST_P(MasterAPITest, GetLoggingLevel)
// Test the logging level toggle and revert after specific toggle duration.
TEST_P(MasterAPITest, SetLoggingLevel)
{
- Try<Owned<cluster::Master>> master = this->StartMaster();
+ master::Flags flags = CreateMasterFlags();
+
+ {
+ // Default principal 2 is not allowed to set the logging level.
+ mesos::ACL::SetLogLevel* acl = flags.acls.get().add_set_log_level();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_level()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Owned<cluster::Master>> master = this->StartMaster(flags);
ASSERT_SOME(master);
// We capture the original logging level first; it would be used to verify
@@ -754,24 +763,37 @@ TEST_P(MasterAPITest, SetLoggingLevel)
setLoggingLevel->mutable_duration()->set_nanoseconds(toggleDuration.ns());
ContentType contentType = GetParam();
- http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
- headers["Accept"] = stringify(contentType);
- Future<Nothing> v1Response = http::post(
- master.get()->pid,
- "api/v1",
- headers,
- serialize(contentType, v1Call),
- stringify(contentType))
- .then([](const http::Response& response) -> Future<Nothing> {
- if (response.status != http::OK().status) {
- return Failure("Unexpected response status " + response.status);
- }
- return Nothing();
- });
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headers["Accept"] = stringify(contentType);
- AWAIT_READY(v1Response);
- ASSERT_EQ(toggleLevel, static_cast<uint32_t>(FLAGS_v));
+ Future<http::Response> v1Response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1Call),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, v1Response);
+ }
+
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> v1Response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1Call),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, v1Response);
+
+ AWAIT_READY(v1Response);
+ ASSERT_EQ(toggleLevel, static_cast<uint32_t>(FLAGS_v));
+ }
// Speedup the logging level revert.
Clock::pause();
[3/6] mesos git commit: Enabled authorization for v1 API call
UPDATE_MAINTENANCE_SCHEDULE.
Posted by ti...@apache.org.
Enabled authorization for v1 API call UPDATE_MAINTENANCE_SCHEDULE.
Enables the use of authorization for the 'UPDATE_MAINTENANCE_SCHEDULE'
v1 API call, using the ACL 'UpdateMaintenanceSchedule' and the action
of the same name as the API call.
It also updates the 'ApiTests' to take into account the authorization
case.
Review: https://reviews.apache.org/r/59099/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/d83a1676
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/d83a1676
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/d83a1676
Branch: refs/heads/master
Commit: d83a1676a15aa3f2fc7377bc33683148ea6654db
Parents: 9bffd0f
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 16 20:30:04 2017 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Jun 16 20:30:04 2017 +0200
----------------------------------------------------------------------
src/master/http.cpp | 180 +++++++++++++++++++++++++++----------------
src/master/master.hpp | 13 +++-
src/tests/api_tests.cpp | 57 +++++++++-----
3 files changed, 166 insertions(+), 84 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/d83a1676/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 857e82b..8fa99ba 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -4026,14 +4026,19 @@ string Master::Http::MAINTENANCE_SCHEDULE_HELP()
"",
"POST: Validates the request body as JSON",
"and updates the maintenance schedule."),
- AUTHENTICATION(true));
+ AUTHENTICATION(true),
+ AUTHORIZATION(
+ "POST: The current principal must be authorized to modify the",
+ "maintenance schedule of all the machines in the request. If the",
+ "principal is unauthorized to modify the schedule for at least one",
+ "machine, the whole request will fail."));
}
// /master/maintenance/schedule endpoint handler.
Future<Response> Master::Http::maintenanceSchedule(
const Request& request,
- const Option<Principal>&) const
+ const Option<Principal>& principal) const
{
// When current master is not the leader, redirect to the leading master.
if (!master->elected()) {
@@ -4064,7 +4069,7 @@ Future<Response> Master::Http::maintenanceSchedule(
return BadRequest(protoSchedule.error());
}
- return _updateMaintenanceSchedule(protoSchedule.get());
+ return _updateMaintenanceSchedule(protoSchedule.get(), principal);
}
@@ -4081,7 +4086,8 @@ mesos::maintenance::Schedule Master::Http::_getMaintenanceSchedule() const
Future<Response> Master::Http::_updateMaintenanceSchedule(
- const mesos::maintenance::Schedule& schedule) const
+ const mesos::maintenance::Schedule& schedule,
+ const Option<process::http::authentication::Principal>& principal) const
{
// Validate that the schedule only transitions machines between
// `UP` and `DRAINING` modes.
@@ -4093,80 +4099,124 @@ Future<Response> Master::Http::_updateMaintenanceSchedule(
return BadRequest(isValid.error());
}
- return master->registrar->apply(Owned<Operation>(
- new maintenance::UpdateSchedule(schedule)))
- .then(defer(master->self(), [=](bool result) -> Future<Response> {
- // See the top comment in "master/maintenance.hpp" for why this check
- // is here, and is appropriate.
- CHECK(result);
+ Future<Owned<ObjectApprover>> approver;
- // Update the master's local state with the new schedule.
- // NOTE: We only add or remove differences between the current schedule
- // and the new schedule. This is because the `MachineInfo` struct
- // holds more information than a maintenance schedule.
- // For example, the `mode` field is not part of a maintenance schedule.
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
- // TODO(josephw): allow more than one schedule.
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::UPDATE_MAINTENANCE_SCHEDULE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
- // Put the machines in the updated schedule into a set.
- // Save the unavailability, to help with updating some machines.
- hashmap<MachineID, Unavailability> unavailabilities;
- foreach (const mesos::maintenance::Window& window, schedule.windows()) {
- foreach (const MachineID& id, window.machine_ids()) {
- unavailabilities[id] = window.unavailability();
- }
- }
+ return approver.then(defer(
+ master->self(),
+ [this, schedule](const Owned<ObjectApprover>& approver) {
+ return __updateMaintenanceSchedule(schedule, approver);
+ }));
+}
- // NOTE: Copies are needed because `updateUnavailability()` in this loop
- // modifies the container.
- foreachkey (const MachineID& id, utils::copy(master->machines)) {
- // Update the `unavailability` for each existing machine, except for
- // machines going from `UP` to `DRAINING` (handled in the next loop).
- // Each machine will only be touched by 1 of the 2 loops here to
- // avoid sending inverse offer twice for a single machine since
- // `updateUnavailability` will trigger an inverse offer.
- // TODO(gyliu513): Merge this logic with `Master::updateUnavailability`,
- // having it in two places results in more conditionals to handle.
- if (unavailabilities.contains(id)) {
- if (master->machines[id].info.mode() == MachineInfo::UP) {
- continue;
- }
+Future<Response> Master::Http::__updateMaintenanceSchedule(
+ const mesos::maintenance::Schedule& schedule,
+ const Owned<ObjectApprover>& approver) const
+{
+ foreach (const mesos::maintenance::Window& window, schedule.windows()) {
+ foreach (const MachineID& machine, window.machine_ids()) {
+ ObjectApprover::Object object;
+ object.machine_id = &machine;
- master->updateUnavailability(id, unavailabilities[id]);
- continue;
- }
+ Try<bool> approved = approver->approved(object);
- // Transition each removed machine back to the `UP` mode and remove the
- // unavailability.
- master->machines[id].info.set_mode(MachineInfo::UP);
- master->updateUnavailability(id, None());
+ if (approved.isError()) {
+ return InternalServerError("Authorization error: " + approved.error());
+ } else if (!approved.get()) {
+ return Forbidden();
}
+ }
+ }
- // Save each new machine, with the unavailability
- // and starting in `DRAINING` mode.
- foreach (const mesos::maintenance::Window& window, schedule.windows()) {
- foreach (const MachineID& id, window.machine_ids()) {
- if (master->machines.contains(id) &&
- master->machines[id].info.mode() != MachineInfo::UP) {
- continue;
- }
+ return master->registrar
+ ->apply(Owned<Operation>(new maintenance::UpdateSchedule(schedule)))
+ .then(defer(master->self(), [this, schedule](bool result) {
+ return ___updateMaintenanceSchedule(schedule, result);
+ }));
+}
- MachineInfo info;
- info.mutable_id()->CopyFrom(id);
- info.set_mode(MachineInfo::DRAINING);
+Future<Response> Master::Http::___updateMaintenanceSchedule(
+ const mesos::maintenance::Schedule& schedule,
+ bool applied) const
+{
+ // See the top comment in "master/maintenance.hpp" for why this check
+ // is here, and is appropriate.
+ CHECK(applied);
+
+ // Update the master's local state with the new schedule.
+ // NOTE: We only add or remove differences between the current schedule
+ // and the new schedule. This is because the `MachineInfo` struct
+ // holds more information than a maintenance schedule.
+ // For example, the `mode` field is not part of a maintenance schedule.
+
+ // TODO(josephw): allow more than one schedule.
+
+ // Put the machines in the updated schedule into a set.
+ // Save the unavailability, to help with updating some machines.
+ hashmap<MachineID, Unavailability> unavailabilities;
+ foreach (const mesos::maintenance::Window& window, schedule.windows()) {
+ foreach (const MachineID& id, window.machine_ids()) {
+ unavailabilities[id] = window.unavailability();
+ }
+ }
- master->machines[id].info.CopyFrom(info);
+ // NOTE: Copies are needed because `updateUnavailability()` in this loop
+ // modifies the container.
+ foreachkey (const MachineID& id, utils::copy(master->machines)) {
+ // Update the `unavailability` for each existing machine, except for
+ // machines going from `UP` to `DRAINING` (handled in the next loop).
+ // Each machine will only be touched by 1 of the 2 loops here to
+ // avoid sending inverse offer twice for a single machine since
+ // `updateUnavailability` will trigger an inverse offer.
+ // TODO(gyliu513): Merge this logic with `Master::updateUnavailability`,
+ // having it in two places results in more conditionals to handle.
+ if (unavailabilities.contains(id)) {
+ if (master->machines[id].info.mode() == MachineInfo::UP) {
+ continue;
+ }
- master->updateUnavailability(id, window.unavailability());
- }
+ master->updateUnavailability(id, unavailabilities[id]);
+ continue;
+ }
+
+ // Transition each removed machine back to the `UP` mode and remove the
+ // unavailability.
+ master->machines[id].info.set_mode(MachineInfo::UP);
+ master->updateUnavailability(id, None());
+ }
+
+ // Save each new machine, with the unavailability
+ // and starting in `DRAINING` mode.
+ foreach (const mesos::maintenance::Window& window, schedule.windows()) {
+ foreach (const MachineID& id, window.machine_ids()) {
+ if (master->machines.contains(id) &&
+ master->machines[id].info.mode() != MachineInfo::UP) {
+ continue;
}
- // Replace the old schedule(s) with the new schedule.
- master->maintenance.schedules.clear();
- master->maintenance.schedules.push_back(schedule);
+ MachineInfo info;
+ info.mutable_id()->CopyFrom(id);
+ info.set_mode(MachineInfo::DRAINING);
- return OK();
- }));
+ master->machines[id].info.CopyFrom(info);
+
+ master->updateUnavailability(id, window.unavailability());
+ }
+ }
+
+ // Replace the old schedule(s) with the new schedule.
+ master->maintenance.schedules.clear();
+ master->maintenance.schedules.push_back(schedule);
+
+ return OK();
}
@@ -4198,7 +4248,7 @@ Future<Response> Master::Http::updateMaintenanceSchedule(
mesos::maintenance::Schedule schedule =
call.update_maintenance_schedule().schedule();
- return _updateMaintenanceSchedule(schedule);
+ return _updateMaintenanceSchedule(schedule, principal);
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/d83a1676/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index e8ddddf..458f825 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -92,6 +92,7 @@ namespace mesos {
// Forward declarations.
class Authorizer;
+class ObjectApprover;
namespace internal {
@@ -1368,7 +1369,17 @@ private:
const FrameworkID& id) const;
process::Future<process::http::Response> _updateMaintenanceSchedule(
- const mesos::maintenance::Schedule& schedule) const;
+ const mesos::maintenance::Schedule& schedule,
+ const Option<process::http::authentication::Principal>&
+ principal) const;
+
+ process::Future<process::http::Response> __updateMaintenanceSchedule(
+ const mesos::maintenance::Schedule& schedule,
+ const process::Owned<ObjectApprover>& approver) const;
+
+ process::Future<process::http::Response> ___updateMaintenanceSchedule(
+ const mesos::maintenance::Schedule& schedule,
+ bool applied) const;
mesos::maintenance::Schedule _getMaintenanceSchedule() const;
http://git-wip-us.apache.org/repos/asf/mesos/blob/d83a1676/src/tests/api_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/api_tests.cpp b/src/tests/api_tests.cpp
index e071700..9f79691 100644
--- a/src/tests/api_tests.cpp
+++ b/src/tests/api_tests.cpp
@@ -1157,12 +1157,18 @@ TEST_P(MasterAPITest, UnreserveResources)
TEST_P(MasterAPITest, UpdateAndGetMaintenanceSchedule)
{
// Set up a master.
- Try<Owned<cluster::Master>> master = this->StartMaster();
- ASSERT_SOME(master);
+ master::Flags flags = CreateMasterFlags();
- ContentType contentType = GetParam();
- http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
- headers["Accept"] = stringify(contentType);
+ {
+ // Default principal 2 is not allowed to update any maintenance schedule.
+ mesos::ACL::UpdateMaintenanceSchedule* acl =
+ flags.acls.get().add_update_maintenance_schedules();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Owned<cluster::Master>> master = StartMaster(flags);
+ ASSERT_SOME(master);
// Generate `MachineID`s that can be used in this test.
MachineID machine1;
@@ -1181,20 +1187,35 @@ TEST_P(MasterAPITest, UpdateAndGetMaintenanceSchedule)
v1UpdateScheduleCall.mutable_update_maintenance_schedule();
maintenanceSchedule->mutable_schedule()->CopyFrom(v1Schedule);
- Future<Nothing> v1UpdateScheduleResponse = http::post(
- master.get()->pid,
- "api/v1",
- headers,
- serialize(contentType, v1UpdateScheduleCall),
- stringify(contentType))
- .then([](const http::Response& response) -> Future<Nothing> {
- if (response.status != http::OK().status) {
- return Failure("Unexpected response status " + response.status);
- }
- return Nothing();
- });
+ ContentType contentType = GetParam();
- AWAIT_READY(v1UpdateScheduleResponse);
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1UpdateScheduleCall),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+ }
+
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1UpdateScheduleCall),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
+ }
// Query maintenance schedule.
v1::master::Call v1GetScheduleCall;
[2/6] mesos git commit: Added authorization support for operator
endpoints.
Posted by ti...@apache.org.
Added authorization support for operator endpoints.
Adds the actions 'UPDATE_MAINTENANCE_SCHEDULE',
'GET_MAINTENANCE_SCHEDULE', 'START_MAINTENANCE', 'STOP_MAINTENANCE'
and 'GET_MAINTENANCE_STATUS' to the authorizer API as well as the
necesary code to handle these new actions.
While the interface 'mesos::Authorizer' takes an object with type
'MachineID' to perform authorization; the default implementation of
the interface 'mesos::LocalAuthorizer' ignores the object choosing the
semantics of allow maintenance on all nodes or none. This was done to
extend the capacities of custom authorizers which may have special
rules for authorization.
Review: https://reviews.apache.org/r/58964/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/9bffd0f3
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/9bffd0f3
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/9bffd0f3
Branch: refs/heads/master
Commit: 9bffd0f36ac036df72d8422550bd4cf2f5831748
Parents: 9ea7fae
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 16 20:29:58 2017 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Jun 16 20:29:58 2017 +0200
----------------------------------------------------------------------
docs/authorization.md | 44 ++++
include/mesos/authorizer/acls.proto | 56 +++++
include/mesos/authorizer/authorizer.hpp | 7 +-
include/mesos/authorizer/authorizer.proto | 16 ++
src/authorizer/local/authorizer.cpp | 126 ++++++++++-
src/tests/authorization_tests.cpp | 291 +++++++++++++++++++++++++
6 files changed, 528 insertions(+), 12 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/9bffd0f3/docs/authorization.md
----------------------------------------------------------------------
diff --git a/docs/authorization.md b/docs/authorization.md
index d94f0f9..da51e8b 100644
--- a/docs/authorization.md
+++ b/docs/authorization.md
@@ -281,6 +281,48 @@ entries, each representing an authorizable action:
</td>
<td>(Re-)registration of agents.</td>
</tr>
+<tr>
+ <td><code>get_maintenance_schedules</code></td>
+ <td>Operator username.</td>
+ <td>Implicitly given. A user should only use types ANY and NONE to allow/deny
+ access to the log.
+ </td>
+ <td>View the maintenance schedule of the machines used by Mesos.</td>
+</tr>
+<tr>
+ <td><code>update_maintenance_schedules</code></td>
+ <td>Operator username.</td>
+ <td>Implicitly given. A user should only use types ANY and NONE to allow/deny
+ access to the log.
+ </td>
+ <td>Modify the maintenance schedule of the machines used by Mesos.</td>
+</tr>
+<tr>
+ <td><code>start_maintenances</code></td>
+ <td>Operator username.</td>
+ <td>Implicitly given. A user should only use types ANY and NONE to allow/deny
+ access to the log.
+ </td>
+ <td>Starts maintenance on a machine. This will make a machine and its agents
+ unavailable.
+ </td>
+</tr>
+<tr>
+ <td><code>stop_maintenances</code></td>
+ <td>Operator username.</td>
+ <td>Implicitly given. A user should only use the types ANY and NONE to
+ allow/deny access to the log.
+ </td>
+ <td>Ends maintenance on a machine.</td>
+</tr>
+<tr>
+ <td><code>get_maintenance_statuses</code></td>
+ <td>Operator username.</td>
+ <td>Implicitly given. A user should only use the types ANY and NONE to
+ allow/deny access to the log.
+ </td>
+ <td>View if a machine is in maintenance or not.</td>
+</tr>
</tbody>
</table>
@@ -813,6 +855,7 @@ message Object {
optional Task task = 3;
optional TaskInfo task_info = 4;
optional ExecutorInfo executor_info = 5;
+ optional MachineID machine_id = 11;
}
```
@@ -849,6 +892,7 @@ struct Object
const Task* task;
const TaskInfo* task_info;
const ExecutorInfo* executor_info;
+ const MachineID* machine_id;
};
```
http://git-wip-us.apache.org/repos/asf/mesos/blob/9bffd0f3/include/mesos/authorizer/acls.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/acls.proto b/include/mesos/authorizer/acls.proto
index 36b3ac2..9109283 100644
--- a/include/mesos/authorizer/acls.proto
+++ b/include/mesos/authorizer/acls.proto
@@ -353,6 +353,57 @@ message ACL {
// access.
required Entity agents = 2;
}
+
+ // Which principals are authorized to update the maintenance schedule.
+ message UpdateMaintenanceSchedule {
+ // Subjects: HTTP Username.
+ required Entity principals = 1;
+
+ // Objects: Given implicitly. Use Entity type ANY or NONE to allow or deny
+ // access.
+ required Entity machines = 2;
+ }
+
+ // Which principals are authorized to view the maintenance schedule.
+ message GetMaintenanceSchedule {
+ // Subjects: HTTP Username.
+ required Entity principals = 1;
+
+ // Objects: Given implicitly. Use Entity type ANY or NONE to allow or deny
+ // access.
+ required Entity machines = 2;
+ }
+
+ // Which principals are authorized to start maintenance on a machine.
+ message StartMaintenance {
+ // Subjects: HTTP Username.
+ required Entity principals = 1;
+
+ // Objects: Given implicitly. Use Entity type ANY or NONE to allow or deny
+ // access.
+ required Entity machines = 2;
+ }
+
+ // Which principals are authorized to stop maintenance on a machine.
+ message StopMaintenance {
+ // Subjects: HTTP Username.
+ required Entity principals = 1;
+
+ // Objects: Given implicitly. Use Entity type ANY or NONE to allow or deny
+ // access.
+ required Entity machines = 2;
+ }
+
+ // Which principals are authorized to get the maintenance status of a
+ // machine.
+ message GetMaintenanceStatus {
+ // Subjects: HTTP Username.
+ required Entity principals = 1;
+
+ // Objects: Given implicitly. Use Entity type ANY or NONE to allow or deny
+ // access.
+ required Entity machines = 2;
+ }
}
@@ -418,4 +469,9 @@ message ACLs {
repeated ACL.SetLogLevel set_log_level = 32;
repeated ACL.RemoveNestedContainer remove_nested_containers = 33;
repeated ACL.RegisterAgent register_agents = 34;
+ repeated ACL.UpdateMaintenanceSchedule update_maintenance_schedules = 35;
+ repeated ACL.GetMaintenanceSchedule get_maintenance_schedules = 36;
+ repeated ACL.StartMaintenance start_maintenances = 37;
+ repeated ACL.StopMaintenance stop_maintenances = 38;
+ repeated ACL.GetMaintenanceStatus get_maintenance_statuses = 39;
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/9bffd0f3/include/mesos/authorizer/authorizer.hpp
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.hpp b/include/mesos/authorizer/authorizer.hpp
index 4a7376f..95cbcf3 100644
--- a/include/mesos/authorizer/authorizer.hpp
+++ b/include/mesos/authorizer/authorizer.hpp
@@ -55,7 +55,8 @@ public:
weight_info(nullptr),
resource(nullptr),
command_info(nullptr),
- container_id(nullptr) {}
+ container_id(nullptr),
+ machine_id(nullptr) {}
Object(const authorization::Object& object)
: value(object.has_value() ? &object.value() : nullptr),
@@ -71,7 +72,8 @@ public:
command_info(
object.has_command_info() ? &object.command_info() : nullptr),
container_id(
- object.has_container_id() ? &object.container_id() : nullptr) {}
+ object.has_container_id() ? &object.container_id() : nullptr),
+ machine_id(object.has_machine_id() ? &object.machine_id() : nullptr) {}
const std::string* value;
const FrameworkInfo* framework_info;
@@ -83,6 +85,7 @@ public:
const Resource* resource;
const CommandInfo* command_info;
const ContainerID* container_id;
+ const MachineID* machine_id;
};
/**
http://git-wip-us.apache.org/repos/asf/mesos/blob/9bffd0f3/include/mesos/authorizer/authorizer.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.proto b/include/mesos/authorizer/authorizer.proto
index c9184d1..38f0e0b 100644
--- a/include/mesos/authorizer/authorizer.proto
+++ b/include/mesos/authorizer/authorizer.proto
@@ -55,6 +55,7 @@ message Object {
optional Resource resource = 8;
optional CommandInfo command_info = 9;
optional ContainerID container_id = 10;
+ optional MachineID machine_id = 11;
}
@@ -197,6 +198,21 @@ enum Action {
// This action will not fill in any object fields, since a principal is
// either allowed to register as an agent or is unauthorized.
REGISTER_AGENT = 28;
+
+ // This action will set objects of type `MachineID`.
+ UPDATE_MAINTENANCE_SCHEDULE = 29;
+
+ // This action will set objects of type `MachineID`.
+ GET_MAINTENANCE_SCHEDULE = 30;
+
+ // This action will set objects of type `MachineID`.
+ START_MAINTENANCE = 31;
+
+ // This action will set objects of type `MachineID`.
+ STOP_MAINTENANCE = 32;
+
+ // This action will set objects of type `MachineID`.
+ GET_MAINTENANCE_STATUS = 33;
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/9bffd0f3/src/authorizer/local/authorizer.cpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.cpp b/src/authorizer/local/authorizer.cpp
index 1f2a990..810566a 100644
--- a/src/authorizer/local/authorizer.cpp
+++ b/src/authorizer/local/authorizer.cpp
@@ -388,12 +388,14 @@ public:
}
break;
- case authorization::SET_LOG_LEVEL:
- aclObject.set_type(mesos::ACL::Entity::ANY);
-
- break;
+ case authorization::GET_MAINTENANCE_SCHEDULE:
+ case authorization::GET_MAINTENANCE_STATUS:
case authorization::REGISTER_AGENT:
- aclObject.set_type(mesos::ACL::Entity::ANY);
+ case authorization::SET_LOG_LEVEL:
+ case authorization::START_MAINTENANCE:
+ case authorization::STOP_MAINTENANCE:
+ case authorization::UPDATE_MAINTENANCE_SCHEDULE:
+ aclObject.set_type(ACL::Entity::ANY);
break;
case authorization::CREATE_VOLUME:
@@ -644,21 +646,26 @@ public:
case authorization::ATTACH_CONTAINER_OUTPUT:
case authorization::DESTROY_VOLUME:
case authorization::GET_ENDPOINT_WITH_PATH:
+ case authorization::GET_MAINTENANCE_SCHEDULE:
+ case authorization::GET_MAINTENANCE_STATUS:
case authorization::KILL_NESTED_CONTAINER:
case authorization::LAUNCH_NESTED_CONTAINER:
case authorization::LAUNCH_NESTED_CONTAINER_SESSION:
+ case authorization::REGISTER_AGENT:
case authorization::REMOVE_NESTED_CONTAINER:
case authorization::RUN_TASK:
case authorization::SET_LOG_LEVEL:
+ case authorization::START_MAINTENANCE:
+ case authorization::STOP_MAINTENANCE:
case authorization::TEARDOWN_FRAMEWORK:
case authorization::UNRESERVE_RESOURCES:
+ case authorization::UPDATE_MAINTENANCE_SCHEDULE:
case authorization::VIEW_CONTAINER:
case authorization::VIEW_EXECUTOR:
case authorization::VIEW_FLAGS:
case authorization::VIEW_FRAMEWORK:
case authorization::VIEW_TASK:
case authorization::WAIT_NESTED_CONTAINER:
- case authorization::REGISTER_AGENT:
case authorization::UNKNOWN:
UNREACHABLE();
}
@@ -849,22 +856,27 @@ public:
case authorization::ATTACH_CONTAINER_OUTPUT:
case authorization::DESTROY_VOLUME:
case authorization::GET_ENDPOINT_WITH_PATH:
+ case authorization::GET_MAINTENANCE_SCHEDULE:
+ case authorization::GET_MAINTENANCE_STATUS:
case authorization::KILL_NESTED_CONTAINER:
case authorization::LAUNCH_NESTED_CONTAINER:
case authorization::LAUNCH_NESTED_CONTAINER_SESSION:
+ case authorization::REGISTER_AGENT:
+ case authorization::REMOVE_NESTED_CONTAINER:
case authorization::RUN_TASK:
case authorization::SET_LOG_LEVEL:
+ case authorization::START_MAINTENANCE:
+ case authorization::STOP_MAINTENANCE:
case authorization::TEARDOWN_FRAMEWORK:
case authorization::UNKNOWN:
case authorization::UNRESERVE_RESOURCES:
+ case authorization::UPDATE_MAINTENANCE_SCHEDULE:
case authorization::VIEW_CONTAINER:
case authorization::VIEW_EXECUTOR:
case authorization::VIEW_FLAGS:
case authorization::VIEW_FRAMEWORK:
case authorization::VIEW_TASK:
case authorization::WAIT_NESTED_CONTAINER:
- case authorization::REMOVE_NESTED_CONTAINER:
- case authorization::REGISTER_AGENT:
UNREACHABLE();
}
@@ -1010,19 +1022,24 @@ public:
case authorization::ATTACH_CONTAINER_OUTPUT:
case authorization::DESTROY_VOLUME:
case authorization::GET_ENDPOINT_WITH_PATH:
+ case authorization::GET_MAINTENANCE_SCHEDULE:
+ case authorization::GET_MAINTENANCE_STATUS:
case authorization::KILL_NESTED_CONTAINER:
+ case authorization::REGISTER_AGENT:
+ case authorization::REMOVE_NESTED_CONTAINER:
case authorization::RUN_TASK:
case authorization::SET_LOG_LEVEL:
+ case authorization::START_MAINTENANCE:
+ case authorization::STOP_MAINTENANCE:
case authorization::TEARDOWN_FRAMEWORK:
case authorization::UNRESERVE_RESOURCES:
+ case authorization::UPDATE_MAINTENANCE_SCHEDULE:
case authorization::VIEW_CONTAINER:
case authorization::VIEW_EXECUTOR:
case authorization::VIEW_FLAGS:
case authorization::VIEW_FRAMEWORK:
case authorization::VIEW_TASK:
case authorization::WAIT_NESTED_CONTAINER:
- case authorization::REMOVE_NESTED_CONTAINER:
- case authorization::REGISTER_AGENT:
case authorization::UNKNOWN: {
Result<vector<GenericACL>> genericACLs =
createGenericACLs(action, acls);
@@ -1248,6 +1265,59 @@ private:
}
return acls_;
+ case authorization::UPDATE_MAINTENANCE_SCHEDULE:
+ foreach (const ACL::UpdateMaintenanceSchedule& acl,
+ acls.update_maintenance_schedules()) {
+ GenericACL acl_;
+ acl_.subjects = acl.principals();
+ acl_.objects = acl.machines();
+
+ acls_.push_back(acl_);
+ }
+
+ return acls_;
+ case authorization::GET_MAINTENANCE_SCHEDULE:
+ foreach (const ACL::GetMaintenanceSchedule& acl,
+ acls.get_maintenance_schedules()) {
+ GenericACL acl_;
+ acl_.subjects = acl.principals();
+ acl_.objects = acl.machines();
+
+ acls_.push_back(acl_);
+ }
+
+ return acls_;
+ case authorization::START_MAINTENANCE:
+ foreach (const ACL::StartMaintenance& acl, acls.start_maintenances()) {
+ GenericACL acl_;
+ acl_.subjects = acl.principals();
+ acl_.objects = acl.machines();
+
+ acls_.push_back(acl_);
+ }
+
+ return acls_;
+ case authorization::STOP_MAINTENANCE:
+ foreach (const ACL::StopMaintenance& acl, acls.stop_maintenances()) {
+ GenericACL acl_;
+ acl_.subjects = acl.principals();
+ acl_.objects = acl.machines();
+
+ acls_.push_back(acl_);
+ }
+
+ return acls_;
+ case authorization::GET_MAINTENANCE_STATUS:
+ foreach (const ACL::GetMaintenanceStatus& acl,
+ acls.get_maintenance_statuses()) {
+ GenericACL acl_;
+ acl_.subjects = acl.principals();
+ acl_.objects = acl.machines();
+
+ acls_.push_back(acl_);
+ }
+
+ return acls_;
case authorization::REGISTER_FRAMEWORK:
case authorization::CREATE_VOLUME:
case authorization::RESERVE_RESOURCES:
@@ -1343,6 +1413,42 @@ Option<Error> LocalAuthorizer::validate(const ACLs& acls)
}
}
+ foreach (const ACL::UpdateMaintenanceSchedule& acl,
+ acls.update_maintenance_schedules()) {
+ if (acl.machines().type() == ACL::Entity::SOME) {
+ return Error(
+ "acls.update_maintenance_schedule type must be either NONE or ANY");
+ }
+ }
+
+ foreach (const ACL::GetMaintenanceSchedule& acl,
+ acls.get_maintenance_schedules()) {
+ if (acl.machines().type() == ACL::Entity::SOME) {
+ return Error(
+ "acls.get_maintenance_schedule type must be either NONE or ANY");
+ }
+ }
+
+ foreach (const ACL::StartMaintenance& acl, acls.start_maintenances()) {
+ if (acl.machines().type() == ACL::Entity::SOME) {
+ return Error("acls.start_maintenance type must be either NONE or ANY");
+ }
+ }
+
+ foreach (const ACL::StopMaintenance& acl, acls.stop_maintenances()) {
+ if (acl.machines().type() == ACL::Entity::SOME) {
+ return Error("acls.stop_maintenance type must be either NONE or ANY");
+ }
+ }
+
+ foreach (const ACL::GetMaintenanceStatus& acl,
+ acls.get_maintenance_statuses()) {
+ if (acl.machines().type() == ACL::Entity::SOME) {
+ return Error(
+ "acls.get_maintenance_status type must be either NONE or ANY");
+ }
+ }
+
// TODO(alexr): Consider validating not only protobuf, but also the original
// JSON in order to spot misspelled names. A misspelled action may affect
// authorization result and hence lead to a security issue (e.g. when there
http://git-wip-us.apache.org/repos/asf/mesos/blob/9bffd0f3/src/tests/authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/authorization_tests.cpp b/src/tests/authorization_tests.cpp
index 583415e..1b5c6e2 100644
--- a/src/tests/authorization_tests.cpp
+++ b/src/tests/authorization_tests.cpp
@@ -4944,6 +4944,297 @@ TYPED_TEST(AuthorizationTest, RegisterAgent)
}
}
+
+// This tests the authorization of requests to UpdateMaintenanceSchedule.
+TYPED_TEST(AuthorizationTest, UpdateMaintenanceSchedule)
+{
+ ACLs acls;
+
+ {
+ // "foo" principal can update maintenance schedule.
+ mesos::ACL::UpdateMaintenanceSchedule* acl =
+ acls.add_update_maintenance_schedules();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::ANY);
+ }
+
+ {
+ // Nobody else can update maintenance schedule.
+ mesos::ACL::UpdateMaintenanceSchedule* acl =
+ acls.add_update_maintenance_schedules();
+ acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+ ASSERT_SOME(create);
+ Owned<Authorizer> authorizer(create.get());
+
+ {
+ // "foo" is allowed to update maintenance schedules.
+ authorization::Request request;
+ request.set_action(authorization::UPDATE_MAINTENANCE_SCHEDULE);
+ request.mutable_subject()->set_value("foo");
+
+ AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // "bar" is not allowed to update maintenance schedules.
+ authorization::Request request;
+ request.set_action(authorization::UPDATE_MAINTENANCE_SCHEDULE);
+ request.mutable_subject()->set_value("bar");
+
+ AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // Test that no authorizer is created with invalid ACLs.
+ ACLs invalid;
+
+ mesos::ACL::UpdateMaintenanceSchedule* acl =
+ invalid.add_update_maintenance_schedules();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->add_values("yoda");
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(invalid));
+ EXPECT_ERROR(create);
+ }
+}
+
+
+// This tests the authorization of requests to GetMaintenanceSchedule.
+TYPED_TEST(AuthorizationTest, GetMaintenanceSchedule)
+{
+ ACLs acls;
+
+ {
+ // "foo" principal can view the maintenance schedule of the whole cluster.
+ mesos::ACL::GetMaintenanceSchedule* acl =
+ acls.add_get_maintenance_schedules();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::ANY);
+ }
+
+ {
+ // Nobody else can view the maintenance schedule.
+ mesos::ACL::GetMaintenanceSchedule* acl =
+ acls.add_get_maintenance_schedules();
+ acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+ ASSERT_SOME(create);
+ Owned<Authorizer> authorizer(create.get());
+
+ {
+ // "foo" is allowed to view maintenance schedules. The request should
+ // succeed.
+ authorization::Request request;
+ request.set_action(authorization::GET_MAINTENANCE_SCHEDULE);
+ request.mutable_subject()->set_value("foo");
+
+ AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // "bar" is not allowed to view maintenance schedules. The request
+ // should fail.
+ authorization::Request request;
+ request.set_action(authorization::GET_MAINTENANCE_SCHEDULE);
+ request.mutable_subject()->set_value("bar");
+
+ AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // Test that no authorizer is created with invalid ACLs.
+ ACLs invalid;
+
+ mesos::ACL::GetMaintenanceSchedule* acl =
+ invalid.add_get_maintenance_schedules();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->add_values("yoda");
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(invalid));
+ EXPECT_ERROR(create);
+ }
+}
+
+
+// This tests the authorization of requests to StartMaintenance.
+TYPED_TEST(AuthorizationTest, StartMaintenance)
+{
+ ACLs acls;
+
+ {
+ // "foo" principal can start maintenance.
+ mesos::ACL::StartMaintenance* acl = acls.add_start_maintenances();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::ANY);
+ }
+
+ {
+ // Nobody else can start maintenance.
+ mesos::ACL::StartMaintenance* acl = acls.add_start_maintenances();
+ acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+ ASSERT_SOME(create);
+ Owned<Authorizer> authorizer(create.get());
+
+ {
+ // "foo" is allowed to start maintenance mode in nodes. The
+ // request should succeed.
+ authorization::Request request;
+ request.set_action(authorization::START_MAINTENANCE);
+ request.mutable_subject()->set_value("foo");
+
+ AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // "bar" is not allowed to start maintenance mode in nodes. The
+ // request should fail.
+ authorization::Request request;
+ request.set_action(authorization::START_MAINTENANCE);
+ request.mutable_subject()->set_value("bar");
+
+ AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // Test that no authorizer is created with invalid ACLs.
+ ACLs invalid;
+
+ mesos::ACL::StartMaintenance* acl = invalid.add_start_maintenances();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->add_values("yoda");
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(invalid));
+ EXPECT_ERROR(create);
+ }
+}
+
+
+// This tests the authorization of requests to StopMaintenance.
+TYPED_TEST(AuthorizationTest, StopMaintenance)
+{
+ ACLs acls;
+
+ {
+ // "foo" principal can stop maintenance.
+ mesos::ACL::StopMaintenance* acl = acls.add_stop_maintenances();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::ANY);
+ }
+
+ {
+ // Nobody else can stop maintenance.
+ mesos::ACL::StopMaintenance* acl = acls.add_stop_maintenances();
+ acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+ ASSERT_SOME(create);
+ Owned<Authorizer> authorizer(create.get());
+
+ {
+ // "foo" is allowed to stop maintenance on nodes. The request
+ // should succeed.
+ authorization::Request request;
+ request.set_action(authorization::STOP_MAINTENANCE);
+ request.mutable_subject()->set_value("foo");
+
+ AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // "bar" is not allowed to stop maintenance on nodes. The request
+ // should fail.
+ authorization::Request request;
+ request.set_action(authorization::STOP_MAINTENANCE);
+ request.mutable_subject()->set_value("bar");
+
+ AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // Test that no authorizer is created with invalid ACLs.
+ ACLs invalid;
+
+ mesos::ACL::StopMaintenance* acl = invalid.add_stop_maintenances();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->add_values("yoda");
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(invalid));
+ EXPECT_ERROR(create);
+ }
+}
+
+
+// This tests the authorization of requests to GetMaintenanceStatus.
+TYPED_TEST(AuthorizationTest, GetMaintenanceStatus)
+{
+ ACLs acls;
+
+ {
+ // "foo" principal view the maintenance status of the whole cluster.
+ mesos::ACL::GetMaintenanceStatus* acl =
+ acls.add_get_maintenance_statuses();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::ANY);
+ }
+
+ {
+ // Nobody else can view the maintenance status.
+ mesos::ACL::GetMaintenanceStatus* acl =
+ acls.add_get_maintenance_statuses();
+ acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+ ASSERT_SOME(create);
+ Owned<Authorizer> authorizer(create.get());
+
+ {
+ // "foo" is allowed to view maintenance status. The request should
+ // succeed.
+ authorization::Request request;
+ request.set_action(authorization::GET_MAINTENANCE_STATUS);
+ request.mutable_subject()->set_value("foo");
+
+ AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // "bar" is not allowed to view maintenance status. The request should fail.
+ authorization::Request request;
+ request.set_action(authorization::GET_MAINTENANCE_STATUS);
+ request.mutable_subject()->set_value("bar");
+
+ AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request));
+ }
+
+ {
+ // Test that no authorizer is created with invalid ACLs.
+ ACLs invalid;
+
+ mesos::ACL::GetMaintenanceStatus* acl =
+ invalid.add_get_maintenance_statuses();
+ acl->mutable_principals()->add_values("foo");
+ acl->mutable_machines()->add_values("yoda");
+
+ Try<Authorizer*> create = TypeParam::create(parameterize(invalid));
+ EXPECT_ERROR(create);
+ }
+}
+
} // namespace tests {
} // namespace internal {
} // namespace mesos {
[6/6] mesos git commit: Enabled authorization for v1 calls starting
and stopping maintenance.
Posted by ti...@apache.org.
Enabled authorization for v1 calls starting and stopping maintenance.
Enables the use of authorization for the 'START_MAINTENANCE' and
'STOP_MAINTENANCE' v1 API calls, using the ACLs 'StartMaintenance' and
'StopMaintenance' respectively as well the actions of the same name as
the API calls.
It also updates the ApiTests to take into account the authorization
case.
Review: https://reviews.apache.org/r/59147/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/def0b106
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/def0b106
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/def0b106
Branch: refs/heads/master
Commit: def0b106a4b0a34138a430e2791f69d4a95f9ab1
Parents: 3f838a1
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 16 20:30:28 2017 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Jun 16 20:30:28 2017 +0200
----------------------------------------------------------------------
src/master/http.cpp | 109 ++++++++++++++++++++---
src/master/master.hpp | 6 +-
src/tests/api_tests.cpp | 132 ++++++++++++++++++----------
src/tests/master_maintenance_tests.cpp | 51 ++++++++++-
4 files changed, 239 insertions(+), 59 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/def0b106/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 0567d54..9a7ebdb 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -4343,14 +4343,17 @@ string Master::Http::MACHINE_DOWN_HELP()
"POST: Validates the request body as JSON and transitions",
" the list of machines into DOWN mode. Currently, only",
" machines in DRAINING mode are allowed to be brought down."),
- AUTHENTICATION(true));
+ AUTHENTICATION(true),
+ AUTHORIZATION(
+ "The current principal must be allowed to bring down all the machines",
+ "in the request, otherwise the request will fail."));
}
// /master/machine/down endpoint handler.
Future<Response> Master::Http::machineDown(
const Request& request,
- const Option<Principal>&) const
+ const Option<Principal>& principal) const
{
// When current master is not the leader, redirect to the leading master.
if (!master->elected()) {
@@ -4373,12 +4376,28 @@ Future<Response> Master::Http::machineDown(
return BadRequest(ids.error());
}
- return _startMaintenance(ids.get());
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::START_MAINTENANCE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ return approver.then(defer(
+ master->self(),
+ [this, ids](const Owned<ObjectApprover>& approver) {
+ return _startMaintenance(ids.get(), approver);
+ }));
}
Future<Response> Master::Http::_startMaintenance(
- const RepeatedPtrField<MachineID>& machineIds) const
+ const RepeatedPtrField<MachineID>& machineIds,
+ const Owned<ObjectApprover>& approver) const
{
// Validate every machine in the list.
Try<Nothing> isValid = maintenance::validation::machines(machineIds);
@@ -4400,6 +4419,16 @@ Future<Response> Master::Http::_startMaintenance(
"Machine '" + stringify(JSON::protobuf(id)) +
"' is not in DRAINING mode and cannot be brought down");
}
+
+ ObjectApprover::Object object;
+ object.machine_id = &id;
+ Try<bool> approved = approver->approved(object);
+
+ if (approved.isError()) {
+ return InternalServerError("Authorization error: " + approved.error());
+ } else if (!approved.get()) {
+ return Forbidden();
+ }
}
return master->registrar->apply(Owned<Operation>(
@@ -4458,9 +4487,24 @@ Future<Response> Master::Http::startMaintenance(
CHECK_EQ(mesos::master::Call::START_MAINTENANCE, call.type());
CHECK(call.has_start_maintenance());
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::START_MAINTENANCE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
RepeatedPtrField<MachineID> machineIds = call.start_maintenance().machines();
- return _startMaintenance(machineIds);
+ return approver.then(defer(
+ master->self(),
+ [this, machineIds](const Owned<ObjectApprover>& approver) {
+ return _startMaintenance(machineIds, approver);
+ }));
}
@@ -4482,14 +4526,17 @@ string Master::Http::MACHINE_UP_HELP()
"POST: Validates the request body as JSON and transitions",
" the list of machines into UP mode. This also removes",
" the list of machines from the maintenance schedule."),
- AUTHENTICATION(true));
+ AUTHENTICATION(true),
+ AUTHORIZATION(
+ "The current principal must be allowed to bring up all the machines",
+ "in the request, otherwise the request will fail."));
}
// /master/machine/up endpoint handler.
Future<Response> Master::Http::machineUp(
const Request& request,
- const Option<Principal>&) const
+ const Option<Principal>& principal) const
{
// When current master is not the leader, redirect to the leading master.
if (!master->elected()) {
@@ -4512,12 +4559,28 @@ Future<Response> Master::Http::machineUp(
return BadRequest(ids.error());
}
- return _stopMaintenance(ids.get());
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::STOP_MAINTENANCE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ return approver.then(defer(
+ master->self(),
+ [this, ids](const Owned<ObjectApprover>& approver) {
+ return _stopMaintenance(ids.get(), approver);
+ }));
}
Future<Response> Master::Http::_stopMaintenance(
- const RepeatedPtrField<MachineID>& machineIds) const
+ const RepeatedPtrField<MachineID>& machineIds,
+ const Owned<ObjectApprover>& approver) const
{
// Validate every machine in the list.
Try<Nothing> isValid = maintenance::validation::machines(machineIds);
@@ -4538,6 +4601,16 @@ Future<Response> Master::Http::_stopMaintenance(
"Machine '" + stringify(JSON::protobuf(id)) +
"' is not in DOWN mode and cannot be brought up");
}
+
+ ObjectApprover::Object object;
+ object.machine_id = &id;
+ Try<bool> approved = approver->approved(object);
+
+ if (approved.isError()) {
+ return InternalServerError("Authorization error: " + approved.error());
+ } else if (!approved.get()) {
+ return Forbidden();
+ }
}
return master->registrar->apply(Owned<Operation>(
@@ -4598,7 +4671,23 @@ Future<Response> Master::Http::stopMaintenance(
RepeatedPtrField<MachineID> machineIds = call.stop_maintenance().machines();
- return _stopMaintenance(machineIds);
+
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::STOP_MAINTENANCE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ return approver.then(defer(
+ master->self(),
+ [this, machineIds](const Owned<ObjectApprover>& approver) {
+ return _stopMaintenance(machineIds, approver);
+ }));
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/def0b106/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index cc5b151..9c1e95c 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -1388,10 +1388,12 @@ private:
const process::Owned<ObjectApprover>& approver) const;
process::Future<process::http::Response> _startMaintenance(
- const google::protobuf::RepeatedPtrField<MachineID>& machineIds) const;
+ const google::protobuf::RepeatedPtrField<MachineID>& machineIds,
+ const process::Owned<ObjectApprover>& approver) const;
process::Future<process::http::Response> _stopMaintenance(
- const google::protobuf::RepeatedPtrField<MachineID>& machineIds) const;
+ const google::protobuf::RepeatedPtrField<MachineID>& machineIds,
+ const process::Owned<ObjectApprover>& approver) const;
process::Future<process::http::Response> _reserve(
const SlaveID& slaveId,
http://git-wip-us.apache.org/repos/asf/mesos/blob/def0b106/src/tests/api_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/api_tests.cpp b/src/tests/api_tests.cpp
index 0bde547..c40f5cc 100644
--- a/src/tests/api_tests.cpp
+++ b/src/tests/api_tests.cpp
@@ -1374,7 +1374,25 @@ TEST_P(MasterAPITest, GetMaintenanceStatus)
TEST_P(MasterAPITest, StartAndStopMaintenance)
{
// Set up a master.
- Try<Owned<cluster::Master>> master = this->StartMaster();
+ master::Flags flags = CreateMasterFlags();
+
+ {
+ // Default principal 2 is not allowed to start maintenance in any machine.
+ mesos::ACL::StartMaintenance* acl =
+ flags.acls.get().add_start_maintenances();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ {
+ // Default principal 2 is not allowed to stop maintenance in any machine.
+ mesos::ACL::StopMaintenance* acl =
+ flags.acls.get().add_stop_maintenances();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Owned<cluster::Master>> master = StartMaster(flags);
ASSERT_SOME(master);
ContentType contentType = GetParam();
@@ -1396,28 +1414,26 @@ TEST_P(MasterAPITest, StartAndStopMaintenance)
createWindow({machine1, machine2}, unavailability),
createWindow({machine3}, unavailability)
});
+
v1::maintenance::Schedule v1Schedule = evolve(schedule);
- v1::master::Call v1UpdateScheduleCall;
- v1UpdateScheduleCall.set_type(v1::master::Call::UPDATE_MAINTENANCE_SCHEDULE);
- v1::master::Call_UpdateMaintenanceSchedule* maintenanceSchedule =
- v1UpdateScheduleCall.mutable_update_maintenance_schedule();
- maintenanceSchedule->mutable_schedule()->CopyFrom(v1Schedule);
+ {
+ v1::master::Call v1UpdateScheduleCall;
+ v1UpdateScheduleCall.set_type(
+ v1::master::Call::UPDATE_MAINTENANCE_SCHEDULE);
+ v1::master::Call_UpdateMaintenanceSchedule* maintenanceSchedule =
+ v1UpdateScheduleCall.mutable_update_maintenance_schedule();
+ maintenanceSchedule->mutable_schedule()->CopyFrom(v1Schedule);
- Future<Nothing> v1UpdateScheduleResponse = http::post(
- master.get()->pid,
- "api/v1",
- headers,
- serialize(contentType, v1UpdateScheduleCall),
- stringify(contentType))
- .then([](const http::Response& response) -> Future<Nothing> {
- if (response.status != http::OK().status) {
- return Failure("Unexpected response status " + response.status);
- }
- return Nothing();
- });
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1UpdateScheduleCall),
+ stringify(contentType));
- AWAIT_READY(v1UpdateScheduleResponse);
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
+ }
// Start maintenance on machine3.
v1::master::Call v1StartMaintenanceCall;
@@ -1426,20 +1442,33 @@ TEST_P(MasterAPITest, StartAndStopMaintenance)
v1StartMaintenanceCall.mutable_start_maintenance();
startMaintenance->add_machines()->CopyFrom(evolve(machine3));
- Future<Nothing> v1StartMaintenanceResponse = http::post(
- master.get()->pid,
- "api/v1",
- headers,
- serialize(contentType, v1StartMaintenanceCall),
- stringify(contentType))
- .then([](const http::Response& response) -> Future<Nothing> {
- if (response.status != http::OK().status) {
- return Failure("Unexpected response status " + response.status);
- }
- return Nothing();
- });
+ {
+ headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1StartMaintenanceCall),
+ stringify(contentType));
- AWAIT_READY(v1StartMaintenanceResponse);
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+ }
+
+ {
+ headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1StartMaintenanceCall),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
+ }
// Stop maintenance on machine3.
v1::master::Call v1StopMaintenanceCall;
@@ -1448,20 +1477,33 @@ TEST_P(MasterAPITest, StartAndStopMaintenance)
v1StopMaintenanceCall.mutable_stop_maintenance();
stopMaintenance->add_machines()->CopyFrom(evolve(machine3));
- Future<Nothing> v1StopMaintenanceResponse = http::post(
- master.get()->pid,
- "api/v1",
- headers,
- serialize(contentType, v1StopMaintenanceCall),
- stringify(contentType))
- .then([](const http::Response& response) -> Future<Nothing> {
- if (response.status != http::OK().status) {
- return Failure("Unexpected response status " + response.status);
- }
- return Nothing();
- });
+ {
+ headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1StopMaintenanceCall),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+ }
- AWAIT_READY(v1StopMaintenanceResponse);
+ {
+ headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1StopMaintenanceCall),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
+ }
// Query maintenance schedule.
v1::master::Call v1GetScheduleCall;
http://git-wip-us.apache.org/repos/asf/mesos/blob/def0b106/src/tests/master_maintenance_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_maintenance_tests.cpp b/src/tests/master_maintenance_tests.cpp
index 139c42b..e7a80ff 100644
--- a/src/tests/master_maintenance_tests.cpp
+++ b/src/tests/master_maintenance_tests.cpp
@@ -79,6 +79,7 @@ using process::Time;
using process::UPID;
using process::http::BadRequest;
+using process::http::Forbidden;
using process::http::OK;
using process::http::Response;
using process::http::Unauthorized;
@@ -729,7 +730,18 @@ TEST_F(MasterMaintenanceTest, EnterMaintenanceMode)
TEST_F(MasterMaintenanceTest, BringDownMachines)
{
// Set up a master.
- Try<Owned<cluster::Master>> master = StartMaster();
+ master::Flags flags = CreateMasterFlags();
+
+ {
+ // Default principal 2 is not allowed to start maintenance in any machine.
+ mesos::ACL::StartMaintenance* acl =
+ flags.acls.get().add_start_maintenances();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ // Set up a master.
+ Try<Owned<cluster::Master>> master = StartMaster(flags);
ASSERT_SOME(master);
// Extra machine used in this test.
@@ -778,8 +790,21 @@ TEST_F(MasterMaintenanceTest, BringDownMachines)
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+ process::http::Headers headersFailure =
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headersFailure["Content-Type"] = "application/json";
+
// Down machine1.
machines = createMachineList({machine1});
+
+ response = process::http::post(
+ master.get()->pid,
+ "machine/down",
+ headersFailure,
+ stringify(machines));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response);
+
response = process::http::post(
master.get()->pid,
"machine/down",
@@ -823,7 +848,17 @@ TEST_F(MasterMaintenanceTest, BringDownMachines)
TEST_F(MasterMaintenanceTest, BringUpMachines)
{
// Set up a master.
- Try<Owned<cluster::Master>> master = StartMaster();
+ master::Flags flags = CreateMasterFlags();
+
+ {
+ // Default principal 2 is not allowed to stop maintenance in any machine.
+ mesos::ACL::StopMaintenance* acl =
+ flags.acls.get().add_stop_maintenances();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Owned<cluster::Master>> master = StartMaster(flags);
ASSERT_SOME(master);
// Try to bring up an unscheduled machine.
@@ -869,6 +904,18 @@ TEST_F(MasterMaintenanceTest, BringUpMachines)
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+ process::http::Headers headersFailure =
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headersFailure["Content-Type"] = "application/json";
+
+ response = process::http::post(
+ master.get()->pid,
+ "machine/up",
+ headersFailure,
+ stringify(machines));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response);
+
// Up machine3.
response = process::http::post(
master.get()->pid,
[4/6] mesos git commit: Enabled authz on API calls which retrieve
maintenance schedules.
Posted by ti...@apache.org.
Enabled authz on API calls which retrieve maintenance schedules.
Enables the use of authorization for the endpoint
'/maintenance/schedule' as well as the 'GET_MAINTENANCE_SCHEDULE' v1
API call, using the ACL 'GetMaintenanceSchedule' and the action of the
same name as the API call.
It also updates the ApiTests to take into account the authorization
case.
Review: https://reviews.apache.org/r/59100/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/17817eff
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/17817eff
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/17817eff
Branch: refs/heads/master
Commit: 17817effae36009d272bf895bd85b85d6c1d6e2a
Parents: d83a167
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 16 20:30:12 2017 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Jun 16 20:30:12 2017 +0200
----------------------------------------------------------------------
src/master/http.cpp | 99 ++++++++++++++++++++++++++++++++++++++------
src/master/master.hpp | 3 +-
src/tests/api_tests.cpp | 60 ++++++++++++++++++++-------
3 files changed, 134 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/17817eff/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 8fa99ba..3c4583a 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -4028,6 +4028,10 @@ string Master::Http::MAINTENANCE_SCHEDULE_HELP()
"and updates the maintenance schedule."),
AUTHENTICATION(true),
AUTHORIZATION(
+ "GET: The response will contain only the maintenance schedule for",
+ "those machines the current principal is allowed to see. If none",
+ "an empty response will be returned.",
+ "",
"POST: The current principal must be authorized to modify the",
"maintenance schedule of all the machines in the request. If the",
"principal is unauthorized to modify the schedule for at least one",
@@ -4051,8 +4055,27 @@ Future<Response> Master::Http::maintenanceSchedule(
// JSON-ify and return the current maintenance schedule.
if (request.method == "GET") {
- const mesos::maintenance::Schedule schedule = _getMaintenanceSchedule();
- return OK(JSON::protobuf(schedule), request.url.query.get("jsonp"));
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::GET_MAINTENANCE_SCHEDULE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ Option<string> jsonp = request.url.query.get("jsonp");
+
+ return approver.then(defer(
+ master->self(),
+ [this, jsonp](
+ const Owned<ObjectApprover>& approver) -> Future<Response> {
+ const mesos::maintenance::Schedule schedule =
+ _getMaintenanceSchedule(approver);
+ return OK(JSON::protobuf(schedule), jsonp);
+ }));
}
// Parse the POST body as JSON.
@@ -4073,13 +4096,45 @@ Future<Response> Master::Http::maintenanceSchedule(
}
-mesos::maintenance::Schedule Master::Http::_getMaintenanceSchedule() const
+mesos::maintenance::Schedule Master::Http::_getMaintenanceSchedule(
+ const Owned<ObjectApprover>& approver) const
{
// TODO(josephw): Return more than one schedule.
- const mesos::maintenance::Schedule schedule =
- master->maintenance.schedules.empty() ?
- mesos::maintenance::Schedule() :
- master->maintenance.schedules.front();
+ if (master->maintenance.schedules.empty()) {
+ return mesos::maintenance::Schedule();
+ }
+
+ mesos::maintenance::Schedule schedule;
+
+ foreach (const mesos::maintenance::Window& window,
+ master->maintenance.schedules.front().windows()) {
+ mesos::maintenance::Window window_;
+
+ foreach (const MachineID& machine_id, window.machine_ids()) {
+ ObjectApprover::Object object;
+ object.machine_id = &machine_id;
+
+ Try<bool> approved = approver->approved(object);
+
+ if (approved.isError()) {
+ LOG(WARNING) << "Error during MachineID authorization: "
+ << approved.error();
+ // TODO(arojas): Consider exposing these errors to the caller.
+ continue;
+ }
+
+ if (!approved.get()) {
+ continue;
+ }
+
+ window_.add_machine_ids()->CopyFrom(machine_id);
+ }
+
+ if (window_.machine_ids_size() > 0) {
+ window_.mutable_unavailability()->CopyFrom(window.unavailability());
+ schedule.add_windows()->CopyFrom(window_);
+ }
+ }
return schedule;
}
@@ -4227,13 +4282,31 @@ Future<Response> Master::Http::getMaintenanceSchedule(
{
CHECK_EQ(mesos::master::Call::GET_MAINTENANCE_SCHEDULE, call.type());
- mesos::master::Response response;
- response.set_type(mesos::master::Response::GET_MAINTENANCE_SCHEDULE);
- response.mutable_get_maintenance_schedule()->mutable_schedule()->CopyFrom(
- _getMaintenanceSchedule());
+ Future<Owned<ObjectApprover>> approver;
- return OK(serialize(contentType, evolve(response)),
- stringify(contentType));
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::GET_MAINTENANCE_SCHEDULE);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ return approver.then(defer(
+ master->self(),
+ [this, contentType](
+ const Owned<ObjectApprover>& approver) -> Future<Response> {
+ mesos::master::Response response;
+
+ response.set_type(mesos::master::Response::GET_MAINTENANCE_SCHEDULE);
+
+ response.mutable_get_maintenance_schedule()->mutable_schedule()
+ ->CopyFrom(_getMaintenanceSchedule(approver));
+
+ return OK(serialize(contentType, evolve(response)),
+ stringify(contentType));
+ }));
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/17817eff/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index 458f825..f81beac 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -1381,7 +1381,8 @@ private:
const mesos::maintenance::Schedule& schedule,
bool applied) const;
- mesos::maintenance::Schedule _getMaintenanceSchedule() const;
+ mesos::maintenance::Schedule _getMaintenanceSchedule(
+ const process::Owned<ObjectApprover>& approver) const;
process::Future<mesos::maintenance::ClusterStatus>
_getMaintenanceStatus() const;
http://git-wip-us.apache.org/repos/asf/mesos/blob/17817eff/src/tests/api_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/api_tests.cpp b/src/tests/api_tests.cpp
index 9f79691..679d7b6 100644
--- a/src/tests/api_tests.cpp
+++ b/src/tests/api_tests.cpp
@@ -1167,6 +1167,14 @@ TEST_P(MasterAPITest, UpdateAndGetMaintenanceSchedule)
acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
}
+ {
+ // Default principal 2 is not allowed to view any maintenance schedule.
+ mesos::ACL::GetMaintenanceSchedule* acl =
+ flags.acls.get().add_get_maintenance_schedules();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
Try<Owned<cluster::Master>> master = StartMaster(flags);
ASSERT_SOME(master);
@@ -1221,22 +1229,46 @@ TEST_P(MasterAPITest, UpdateAndGetMaintenanceSchedule)
v1::master::Call v1GetScheduleCall;
v1GetScheduleCall.set_type(v1::master::Call::GET_MAINTENANCE_SCHEDULE);
- Future<v1::master::Response> v1GetScheduleResponse =
- post(master.get()->pid, v1GetScheduleCall, contentType);
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headers["Accept"] = stringify(contentType);
- AWAIT_READY(v1GetScheduleResponse);
- ASSERT_TRUE(v1GetScheduleResponse->IsInitialized());
- ASSERT_EQ(
- v1::master::Response::GET_MAINTENANCE_SCHEDULE,
- v1GetScheduleResponse->type());
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1GetScheduleCall),
+ stringify(contentType));
- // Verify maintenance schedule matches the expectation.
- v1::maintenance::Schedule respSchedule =
- v1GetScheduleResponse->get_maintenance_schedule().schedule();
- ASSERT_EQ(1, respSchedule.windows().size());
- ASSERT_EQ(2, respSchedule.windows(0).machine_ids().size());
- ASSERT_EQ("Machine1", respSchedule.windows(0).machine_ids(0).hostname());
- ASSERT_EQ("0.0.0.2", respSchedule.windows(0).machine_ids(1).ip());
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
+
+ Future<v1::master::Response> v1Response =
+ deserialize<v1::master::Response>(contentType, response->body);
+
+ AWAIT_READY(v1Response);
+ v1::maintenance::Schedule schedule =
+ v1Response->get_maintenance_schedule().schedule();
+ ASSERT_EQ(0, schedule.windows().size());
+ }
+
+ {
+ Future<v1::master::Response> response =
+ post(master.get()->pid, v1GetScheduleCall, contentType);
+
+ AWAIT_READY(response);
+ ASSERT_TRUE(response->IsInitialized());
+ ASSERT_EQ(
+ v1::master::Response::GET_MAINTENANCE_SCHEDULE,
+ response->type());
+
+ // Verify maintenance schedule matches the expectation.
+ v1::maintenance::Schedule schedule =
+ response->get_maintenance_schedule().schedule();
+ ASSERT_EQ(1, schedule.windows().size());
+ ASSERT_EQ(2, schedule.windows(0).machine_ids().size());
+ ASSERT_EQ("Machine1", schedule.windows(0).machine_ids(0).hostname());
+ ASSERT_EQ("0.0.0.2", schedule.windows(0).machine_ids(1).ip());
+ }
}
[5/6] mesos git commit: Enabled authorization when accesing
maintenance status in v0 and v1 API.
Posted by ti...@apache.org.
Enabled authorization when accesing maintenance status in v0 and v1 API.
Enables the use of authorization for the 'GET_MAINTENANCE_STATUS' v1
API call, as well as to the 'maintenance/status' endpoint, using the
ACL 'GetMaintenanceStatus' and the action of the same name as the API
call.
It also updates the ApiTests to take into account the authorization
case.
Review: https://reviews.apache.org/r/59101/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/3f838a15
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/3f838a15
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/3f838a15
Branch: refs/heads/master
Commit: 3f838a15f0183bc97ffe040e3baa4942e8df90e7
Parents: 17817ef
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 16 20:30:20 2017 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Jun 16 20:30:20 2017 +0200
----------------------------------------------------------------------
src/master/http.cpp | 84 +++++++++++++++++++++++------
src/master/master.hpp | 4 +-
src/tests/api_tests.cpp | 60 ++++++++++++++++-----
src/tests/master_maintenance_tests.cpp | 38 +++++++++++--
4 files changed, 149 insertions(+), 37 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/3f838a15/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 3c4583a..0567d54 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -4623,14 +4623,18 @@ string Master::Http::MAINTENANCE_STATUS_HELP()
"**NOTE**:",
"Inverse offer responses are cleared if the master fails over.",
"However, new inverse offers will be sent once the master recovers."),
- AUTHENTICATION(true));
+ AUTHENTICATION(true),
+ AUTHORIZATION(
+ "The response will contain only the maintenance status for those",
+ "machines the current principal is allowed to see. If none, an empty",
+ "response will be returned."));
}
// /master/maintenance/status endpoint handler.
Future<Response> Master::Http::maintenanceStatus(
const Request& request,
- const Option<Principal>&) const
+ const Option<Principal>& principal) const
{
// When current master is not the leader, redirect to the leading master.
if (!master->elected()) {
@@ -4641,16 +4645,33 @@ Future<Response> Master::Http::maintenanceStatus(
return MethodNotAllowed({"GET"}, request.method);
}
- return _getMaintenanceStatus()
- .then([request](const mesos::maintenance::ClusterStatus& status)
- -> Response {
- return OK(JSON::protobuf(status), request.url.query.get("jsonp"));
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::GET_MAINTENANCE_STATUS);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ Option<string> jsonp = request.url.query.get("jsonp");
+
+ return approver
+ .then(defer(
+ master->self(),
+ [this](const Owned<ObjectApprover>& approver) {
+ return _getMaintenanceStatus(approver);
+ }))
+ .then([jsonp](const mesos::maintenance::ClusterStatus& status) -> Response {
+ return OK(JSON::protobuf(status), jsonp);
});
}
-Future<mesos::maintenance::ClusterStatus>
- Master::Http::_getMaintenanceStatus() const
+Future<mesos::maintenance::ClusterStatus> Master::Http::_getMaintenanceStatus(
+ const Owned<ObjectApprover>& approver) const
{
return master->allocator->getInverseOfferStatuses()
.then(defer(
@@ -4667,6 +4688,21 @@ Future<mesos::maintenance::ClusterStatus>
const MachineID& id,
const Machine& machine,
master->machines) {
+ ObjectApprover::Object object;
+ object.machine_id = &id;
+ Try<bool> approved = approver->approved(object);
+
+ if (approved.isError()) {
+ LOG(WARNING) << "Error during MachineID authorization: "
+ << approved.error();
+ // TODO(arojas): Consider exposing these errors to the caller.
+ continue;
+ }
+
+ if (!approved.get()) {
+ continue;
+ }
+
switch (machine.info.mode()) {
case MachineInfo::DRAINING: {
mesos::maintenance::ClusterStatus::DrainingMachine* drainingMachine =
@@ -4712,17 +4748,33 @@ Future<Response> Master::Http::getMaintenanceStatus(
{
CHECK_EQ(mesos::master::Call::GET_MAINTENANCE_STATUS, call.type());
- return _getMaintenanceStatus()
+ Future<Owned<ObjectApprover>> approver;
+
+ if (master->authorizer.isSome()) {
+ Option<authorization::Subject> subject = createSubject(principal);
+
+ approver = master->authorizer.get()->getObjectApprover(
+ subject, authorization::GET_MAINTENANCE_STATUS);
+ } else {
+ approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
+
+ return approver
+ .then(defer(
+ master->self(),
+ [this](const Owned<ObjectApprover>& approver) {
+ return _getMaintenanceStatus(approver);
+ }))
.then([contentType](const mesos::maintenance::ClusterStatus& status)
-> Response {
- mesos::master::Response response;
- response.set_type(mesos::master::Response::GET_MAINTENANCE_STATUS);
- response.mutable_get_maintenance_status()->mutable_status()
- ->CopyFrom(status);
+ mesos::master::Response response;
+ response.set_type(mesos::master::Response::GET_MAINTENANCE_STATUS);
+ response.mutable_get_maintenance_status()->mutable_status()
+ ->CopyFrom(status);
- return OK(serialize(contentType, evolve(response)),
- stringify(contentType));
- });
+ return OK(serialize(contentType, evolve(response)),
+ stringify(contentType));
+ });
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/3f838a15/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index f81beac..cc5b151 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -1384,8 +1384,8 @@ private:
mesos::maintenance::Schedule _getMaintenanceSchedule(
const process::Owned<ObjectApprover>& approver) const;
- process::Future<mesos::maintenance::ClusterStatus>
- _getMaintenanceStatus() const;
+ process::Future<mesos::maintenance::ClusterStatus> _getMaintenanceStatus(
+ const process::Owned<ObjectApprover>& approver) const;
process::Future<process::http::Response> _startMaintenance(
const google::protobuf::RepeatedPtrField<MachineID>& machineIds) const;
http://git-wip-us.apache.org/repos/asf/mesos/blob/3f838a15/src/tests/api_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/api_tests.cpp b/src/tests/api_tests.cpp
index 679d7b6..0bde547 100644
--- a/src/tests/api_tests.cpp
+++ b/src/tests/api_tests.cpp
@@ -1276,7 +1276,17 @@ TEST_P(MasterAPITest, UpdateAndGetMaintenanceSchedule)
TEST_P(MasterAPITest, GetMaintenanceStatus)
{
// Set up a master.
- Try<Owned<cluster::Master>> master = this->StartMaster();
+ master::Flags flags = CreateMasterFlags();
+
+ {
+ // Default principal 2 is not allowed to view any maintenance status.
+ mesos::ACL::GetMaintenanceStatus* acl =
+ flags.acls.get().add_get_maintenance_statuses();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ Try<Owned<cluster::Master>> master = StartMaster(flags);
ASSERT_SOME(master);
ContentType contentType = GetParam();
@@ -1319,20 +1329,42 @@ TEST_P(MasterAPITest, GetMaintenanceStatus)
v1::master::Call v1GetStatusCall;
v1GetStatusCall.set_type(v1::master::Call::GET_MAINTENANCE_STATUS);
- Future<v1::master::Response> v1GetStatusResponse =
- post(master.get()->pid, v1GetStatusCall, contentType);
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL_2);
+ headers["Accept"] = stringify(contentType);
- AWAIT_READY(v1GetStatusResponse);
- ASSERT_TRUE(v1GetStatusResponse->IsInitialized());
- ASSERT_EQ(
- v1::master::Response::GET_MAINTENANCE_STATUS,
- v1GetStatusResponse->type());
-
- // Verify maintenance status matches the expectation.
- v1::maintenance::ClusterStatus status =
- v1GetStatusResponse->get_maintenance_status().status();
- ASSERT_EQ(2, status.draining_machines().size());
- ASSERT_EQ(0, status.down_machines().size());
+ Future<http::Response> response = http::post(
+ master.get()->pid,
+ "api/v1",
+ headers,
+ serialize(contentType, v1GetStatusCall),
+ stringify(contentType));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response);
+
+ Try<v1::master::Response> v1Response =
+ deserialize<v1::master::Response>(contentType, response->body);
+
+ v1::maintenance::ClusterStatus status =
+ v1Response->get_maintenance_status().status();
+ ASSERT_EQ(0, status.draining_machines().size());
+ ASSERT_EQ(0, status.down_machines().size());
+ }
+
+ {
+ Future<v1::master::Response> v1Response =
+ post(master.get()->pid, v1GetStatusCall, contentType);
+
+ AWAIT_READY(v1Response);
+ ASSERT_TRUE(v1Response->IsInitialized());
+ ASSERT_EQ(v1::master::Response::GET_MAINTENANCE_STATUS, v1Response->type());
+
+ // Verify maintenance status matches the expectation.
+ v1::maintenance::ClusterStatus status =
+ v1Response->get_maintenance_status().status();
+ ASSERT_EQ(2, status.draining_machines().size());
+ ASSERT_EQ(0, status.down_machines().size());
+ }
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/3f838a15/src/tests/master_maintenance_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_maintenance_tests.cpp b/src/tests/master_maintenance_tests.cpp
index 9edd74a..139c42b 100644
--- a/src/tests/master_maintenance_tests.cpp
+++ b/src/tests/master_maintenance_tests.cpp
@@ -943,7 +943,18 @@ TEST_F(MasterMaintenanceTest, BringUpMachines)
TEST_F(MasterMaintenanceTest, MachineStatus)
{
// Set up a master.
- Try<Owned<cluster::Master>> master = StartMaster();
+ master::Flags flags = CreateMasterFlags();
+
+ {
+ // Default principal 2 is not allowed to view any maintenance status.
+ mesos::ACL::GetMaintenanceStatus* acl =
+ flags.acls.get().add_get_maintenance_statuses();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_machines()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ // Set up a master.
+ Try<Owned<cluster::Master>> master = StartMaster(flags);
ASSERT_SOME(master);
// Try to stop maintenance on an unscheduled machine.
@@ -958,6 +969,25 @@ TEST_F(MasterMaintenanceTest, MachineStatus)
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+ // Get the maintenance statuses for unauthorized principal.
+ response = process::http::get(
+ master.get()->pid,
+ "maintenance/status",
+ None(),
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL_2));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+
+ Try<JSON::Object> statuses_ = JSON::parse<JSON::Object>(response->body);
+
+ ASSERT_SOME(statuses_);
+ Try<maintenance::ClusterStatus> statuses =
+ ::protobuf::parse<maintenance::ClusterStatus>(statuses_.get());
+
+ ASSERT_SOME(statuses);
+ ASSERT_EQ(0, statuses->draining_machines().size());
+ ASSERT_EQ(0, statuses->down_machines().size());
+
// Get the maintenance statuses.
response = process::http::get(
master.get()->pid,
@@ -968,12 +998,10 @@ TEST_F(MasterMaintenanceTest, MachineStatus)
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
// Check that both machines are draining.
- Try<JSON::Object> statuses_ =
- JSON::parse<JSON::Object>(response->body);
+ statuses_ = JSON::parse<JSON::Object>(response->body);
ASSERT_SOME(statuses_);
- Try<maintenance::ClusterStatus> statuses =
- ::protobuf::parse<maintenance::ClusterStatus>(statuses_.get());
+ statuses = ::protobuf::parse<maintenance::ClusterStatus>(statuses_.get());
ASSERT_SOME(statuses);
ASSERT_EQ(2, statuses->draining_machines().size());