diff options
author | Øyvind Grønnesby <oyving@verizonmedia.com> | 2019-10-09 09:18:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-09 09:18:37 +0200 |
commit | f5a5dca146d8161462c144f31bfb4a91b8a5167a (patch) | |
tree | cffa686fd83ddb9ebf73f59fcc252fba52d8c268 /controller-server | |
parent | 3311d0878eaa98062a805d910f226f263661df87 (diff) | |
parent | 8ce9c7a0b9d90dfb1e8a64f5c623167f0062c3ca (diff) |
Merge pull request #10921 from vespa-engine/ogronnesby/simplified-roles
Introduce simplified roles without removing old ones
Diffstat (limited to 'controller-server')
9 files changed, 83 insertions, 140 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java index 7ad2e03ef1d..a41103453eb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java @@ -20,13 +20,10 @@ import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; import com.yahoo.yolean.Exceptions; -import java.security.Principal; import java.security.PublicKey; import java.util.Base64; -import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.logging.Logger; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java index 77622df4c4a..59cfc188ebe 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java @@ -192,12 +192,11 @@ public class UserApiHandler extends LoggingRequestHandler { UserId user = new UserId(require("user", Inspector::asString, requestObject)); Role role = Roles.toRole(TenantName.from(tenantName), roleName); - if ( role.definition() == RoleDefinition.tenantOwner + if ( role.definition() == RoleDefinition.administrator && Set.of(user.value()).equals(users.listUsers(role).stream().map(User::email).collect(Collectors.toSet()))) - throw new IllegalArgumentException("Can't remove the last owner of a tenant."); + throw new IllegalArgumentException("Can't remove the last administrator of a tenant."); - // TODO jonmv: Change to developer role, when this exists. - if (role.definition().equals(RoleDefinition.tenantOperator)) + if (role.definition().equals(RoleDefinition.developer)) controller.tenants().lockIfPresent(TenantName.from(tenantName), LockedTenant.Cloud.class, tenant -> { PublicKey key = tenant.get().developerKeys().inverse().get(new SimplePrincipal(user.value())); if (key != null) @@ -235,6 +234,10 @@ public class UserApiHandler extends LoggingRequestHandler { case applicationOperator: return "applicationOperator"; case applicationDeveloper: return "applicationDeveloper"; case applicationReader: return "applicationReader"; + case administrator: return "administrator"; + case developer: return "developer"; + case reader: return "reader"; + case headless: return "headless"; default: throw new IllegalArgumentException("Unexpected role type '" + role.definition() + "'."); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java index a88e38e5f89..363dc348ad3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java @@ -36,9 +36,14 @@ public class CloudAccessControl implements AccessControl { CloudTenantSpec spec = (CloudTenantSpec) tenantSpec; CloudTenant tenant = CloudTenant.create(spec.tenant(), defaultBillingInfo); - for (Role role : Roles.tenantRoles(spec.tenant())) + for (Role role : Roles.tenantRoles(spec.tenant())) { userManagement.createRole(role); - userManagement.addUsers(Role.tenantOwner(spec.tenant()), List.of(new UserId(credentials.user().getName()))); + } + + var userId = List.of(new UserId(credentials.user().getName())); + userManagement.addUsers(Role.administrator(spec.tenant()), userId); + userManagement.addUsers(Role.developer(spec.tenant()), userId); + userManagement.addUsers(Role.reader(spec.tenant()), userId); return tenant; } @@ -60,7 +65,6 @@ public class CloudAccessControl implements AccessControl { public void createApplication(TenantAndApplicationId id, Credentials credentials) { for (Role role : Roles.applicationRoles(id.tenant(), id.application())) userManagement.createRole(role); - userManagement.addUsers(Role.applicationAdmin(id.tenant(), id.application()), List.of(new UserId(credentials.user().getName()))); } @Override diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java index b1f5f33b960..d98558d53dc 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java @@ -64,7 +64,7 @@ public class UserApiTest extends ControllerContainerCloudTest { // POST a tenant is available to operators. tester.assertResponse(request("/application/v4/tenant/my-tenant", POST) .roles(operator) - .user("owner@tenant") + .user("administrator@tenant") .data("{\"token\":\"hello\"}"), new File("tenant-without-applications.json")); @@ -79,128 +79,116 @@ public class UserApiTest extends ControllerContainerCloudTest { // POST a hosted operator role is not allowed. tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) - .roles(Set.of(Role.tenantOwner(id.tenant()))) + .roles(Set.of(Role.administrator(id.tenant()))) .data("{\"user\":\"evil@evil\",\"roleName\":\"hostedOperator\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'hostedOperator'.\"}", 400); - // POST a tenant operator is available to the tenant owner. + // POST a tenant developer is available to the tenant owner. tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) - .roles(Set.of(Role.tenantOwner(id.tenant()))) - .data("{\"user\":\"operator@tenant\",\"roleName\":\"tenantOperator\"}"), - "{\"message\":\"user 'operator@tenant' is now a member of role 'tenantOperator' of 'my-tenant'\"}"); + .roles(Set.of(Role.administrator(id.tenant()))) + .data("{\"user\":\"developer@tenant\",\"roleName\":\"developer\"}"), + "{\"message\":\"user 'developer@tenant' is now a member of role 'developer' of 'my-tenant'\"}"); - // POST a tenant admin is not available to a tenant operator. + // POST a tenant admin is not available to a tenant developer. tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) - .roles(Set.of(Role.tenantOperator(id.tenant()))) - .data("{\"user\":\"admin@tenant\",\"roleName\":\"tenantAdmin\"}"), + .roles(Set.of(Role.developer(id.tenant()))) + .data("{\"user\":\"developer@tenant\",\"roleName\":\"administrator\"}"), accessDenied, 403); - // POST an application admin for a non-existent application fails. + // POST a headless for a non-existent application fails. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) - .roles(Set.of(Role.tenantOwner(TenantName.from("my-tenant")))) - .data("{\"user\":\"admin@app\",\"roleName\":\"applicationAdmin\"}"), + .roles(Set.of(Role.administrator(TenantName.from("my-tenant")))) + .data("{\"user\":\"headless@app\",\"roleName\":\"headless\"}"), "{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"NullPointerException\"}", 500); - // POST an application is allowed for a tenant operator. + // POST an application is allowed for a tenant developer. tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", POST) - .user("operator@tenant") - .roles(Set.of(Role.tenantOperator(id.tenant()))), + .user("developer@tenant") + .roles(Set.of(Role.developer(id.tenant()))), new File("application-created.json")); // POST an application is not allowed under a different tenant. tester.assertResponse(request("/application/v4/tenant/other-tenant/application/my-app", POST) - .roles(Set.of(Role.tenantOperator(id.tenant()))), + .roles(Set.of(Role.administrator(id.tenant()))), accessDenied, 403); - // POST an application role is allowed for a tenant admin. - tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) - .roles(Set.of(Role.tenantAdmin(id.tenant()))) - .data("{\"user\":\"reader@app\",\"roleName\":\"applicationReader\"}"), - "{\"message\":\"user 'reader@app' is now a member of role 'applicationReader' of 'my-app' owned by 'my-tenant'\"}"); - // POST a tenant role is not allowed to an application. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) .roles(Set.of(Role.hostedOperator())) - .data("{\"user\":\"reader@app\",\"roleName\":\"tenantOperator\"}"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'tenantOperator'.\"}", 400); + .data("{\"user\":\"developer@app\",\"roleName\":\"developer\"}"), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'developer'.\"}", 400); - // GET tenant role information is available to application readers. + // GET tenant role information is available to readers. tester.assertResponse(request("/user/v1/tenant/my-tenant") - .roles(Set.of(Role.applicationReader(id.tenant(), id.application()))), + .roles(Set.of(Role.reader(id.tenant()))), new File("tenant-roles.json")); - // GET application role information is available to tenant operators. + // GET application role information is available to tenant administrators. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app") - .roles(Set.of(Role.tenantOperator(id.tenant()))), + .roles(Set.of(Role.administrator(id.tenant()))), new File("application-roles.json")); // GET application role information is available also under the /api prefix. tester.assertResponse(request("/api/user/v1/tenant/my-tenant/application/my-app") - .roles(Set.of(Role.tenantOperator(id.tenant()))), + .roles(Set.of(Role.administrator(id.tenant()))), new File("application-roles.json")); // POST a pem deploy key tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app/key", POST) - .roles(Set.of(Role.tenantOperator(id.tenant()))) + .roles(Set.of(Role.developer(id.tenant()))) .data("{\"key\":\"" + pemPublicKey + "\"}"), new File("first-deploy-key.json")); // POST a pem developer key tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST) .user("joe@dev") - .roles(Set.of(Role.tenantOperator(id.tenant()))) + .roles(Set.of(Role.developer(id.tenant()))) .data("{\"key\":\"" + pemPublicKey + "\"}"), new File("first-developer-key.json")); // POST the same pem developer key for a different user is forbidden tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST) .user("operator@tenant") - .roles(Set.of(Role.tenantOperator(id.tenant()))) + .roles(Set.of(Role.developer(id.tenant()))) .data("{\"key\":\"" + pemPublicKey + "\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Key "+ quotedPemPublicKey + " is already owned by joe@dev\"}", 400); - // PATCH in a different pem developer key + // POST in a different pem developer key tester.assertResponse(request("/application/v4/tenant/my-tenant/key", POST) - .user("operator@tenant") - .roles(Set.of(Role.tenantOperator(id.tenant()))) + .user("developer@tenant") + .roles(Set.of(Role.developer(id.tenant()))) .data("{\"key\":\"" + otherPemPublicKey + "\"}"), new File("both-developer-keys.json")); // GET tenant information with keys tester.assertResponse(request("/application/v4/tenant/my-tenant/") - .roles(Set.of(Role.applicationReader(id.tenant(), id.application()))), + .roles(Set.of(Role.reader(id.tenant()))), new File("tenant-with-keys.json")); // DELETE a pem developer key tester.assertResponse(request("/application/v4/tenant/my-tenant/key", DELETE) - .roles(Set.of(Role.tenantOperator(id.tenant()))) + .roles(Set.of(Role.developer(id.tenant()))) .data("{\"key\":\"" + pemPublicKey + "\"}"), new File("second-developer-key.json")); - // DELETE an application role is allowed for an application admin. - tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", DELETE) - .roles(Set.of(Role.applicationAdmin(id.tenant(), id.application()))) - .data("{\"user\":\"operator@tenant\",\"roleName\":\"applicationAdmin\"}"), - "{\"message\":\"user 'operator@tenant' is no longer a member of role 'applicationAdmin' of 'my-app' owned by 'my-tenant'\"}"); - - // DELETE an application is available to application admins. + // DELETE an application is available to developers. tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", DELETE) - .roles(Set.of(Role.applicationAdmin(id.tenant(), id.application()))), + .roles(Set.of(Role.developer(id.tenant()))), "{\"message\":\"Deleted application my-tenant.my-app\"}"); // DELETE a tenant role is available to tenant admins. // DELETE the developer role clears any developer key. tester.assertResponse(request("/user/v1/tenant/my-tenant", DELETE) - .roles(Set.of(Role.tenantAdmin(id.tenant()))) - .data("{\"user\":\"operator@tenant\",\"roleName\":\"tenantOperator\"}"), - "{\"message\":\"user 'operator@tenant' is no longer a member of role 'tenantOperator' of 'my-tenant'\"}"); + .roles(Set.of(Role.administrator(id.tenant()))) + .data("{\"user\":\"developer@tenant\",\"roleName\":\"developer\"}"), + "{\"message\":\"user 'developer@tenant' is no longer a member of role 'developer' of 'my-tenant'\"}"); // DELETE the last tenant owner is not allowed. tester.assertResponse(request("/user/v1/tenant/my-tenant", DELETE) .roles(operator) - .data("{\"user\":\"owner@tenant\",\"roleName\":\"tenantOwner\"}"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Can't remove the last owner of a tenant.\"}", 400); + .data("{\"user\":\"administrator@tenant\",\"roleName\":\"administrator\"}"), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Can't remove the last administrator of a tenant.\"}", 400); // DELETE the tenant is available to the tenant owner. tester.assertResponse(request("/application/v4/tenant/my-tenant", DELETE) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json index e23ab918135..e05156e3eef 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json @@ -2,77 +2,28 @@ "tenant": "my-tenant", "application": "my-app", "roleNames": [ - "applicationAdmin", - "applicationOperator", - "applicationDeveloper", - "applicationReader" + "headless" ], "users": [ { - "name": "owner@tenant", - "email":"owner@tenant", + "name": "administrator@tenant", + "email": "administrator@tenant", "roles": { - "applicationAdmin": { + "headless": { "explicit": false, - "implied": true - }, - "applicationOperator": { - "explicit": false, - "implied": true - }, - "applicationDeveloper": { - "explicit": false, - "implied": true - }, - "applicationReader": { - "explicit": false, - "implied": true - } - } - }, - { - "name": "operator@tenant", - "email":"operator@tenant", - "roles": { - "applicationAdmin": { - "explicit": true, "implied": false - }, - "applicationOperator": { - "explicit": false, - "implied": true - }, - "applicationDeveloper": { - "explicit": false, - "implied": true - }, - "applicationReader": { - "explicit": false, - "implied": true } } }, { - "name": "reader@app", - "email":"reader@app", + "name": "developer@tenant", + "email": "developer@tenant", "roles": { - "applicationAdmin": { - "explicit": false, - "implied": false - }, - "applicationOperator": { - "explicit": false, - "implied": false - }, - "applicationDeveloper": { + "headless": { "explicit": false, "implied": false - }, - "applicationReader": { - "explicit": true, - "implied": false } } } ] -} +}
\ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json index 2ff1c29fe29..bc49135a1db 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/both-developer-keys.json @@ -6,7 +6,7 @@ }, { "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\n-----END PUBLIC KEY-----\n", - "user": "operator@tenant" + "user": "developer@tenant" } ] } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json index f7d90f31116..64098a775a1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/second-developer-key.json @@ -2,7 +2,7 @@ "keys": [ { "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\n-----END PUBLIC KEY-----\n", - "user": "operator@tenant" + "user": "developer@tenant" } ] } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json index fa528fe2ab7..61c1c94d4ca 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json @@ -1,46 +1,46 @@ { "tenant": "my-tenant", "roleNames": [ - "tenantOwner", - "tenantAdmin", - "tenantOperator" + "administrator", + "developer", + "reader" ], "users": [ { - "name": "owner@tenant", - "email":"owner@tenant", + "name": "administrator@tenant", + "email": "administrator@tenant", "roles": { - "tenantOwner": { + "administrator": { "explicit": true, "implied": false }, - "tenantAdmin": { - "explicit": false, - "implied": true + "developer": { + "explicit": true, + "implied": false }, - "tenantOperator": { - "explicit": false, - "implied": true + "reader": { + "explicit": true, + "implied": false } } }, { - "name": "operator@tenant", - "email":"operator@tenant", + "name": "developer@tenant", + "email": "developer@tenant", "roles": { - "tenantOwner": { + "administrator": { "explicit": false, "implied": false }, - "tenantAdmin": { - "explicit": false, + "developer": { + "explicit": true, "implied": false }, - "tenantOperator": { - "explicit": true, + "reader": { + "explicit": false, "implied": false } } } ] -} +}
\ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json index b7970a48963..24ac6633f6c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json @@ -8,7 +8,7 @@ }, { "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\n-----END PUBLIC KEY-----\n", - "user": "operator@tenant" + "user": "developer@tenant" }], "applications": [] } |