diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2017-10-16 12:13:36 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-16 12:13:36 +0200 |
commit | fa59545b68e03ebd3568dd33ca40624ecd05dd35 (patch) | |
tree | e5579a0c2e1d85e796d4751a8f224f97b24ec36d | |
parent | a21f0df17e4ac59244a153ca4ba2ad0dc8ebee74 (diff) | |
parent | 8e22dbbac60165e2c887e05d33961ed6fa4b6a0f (diff) |
Merge pull request #3758 from vespa-engine/bjorncs/revert-athenz-open-source
Revert "Open-source Athenz integration for controller-server"
54 files changed, 493 insertions, 1483 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java index 194088df7dc..eb8b5c5256b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/AthensDomain.java @@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.api.identifiers; /** * @author smorgrav */ -// TODO Rename to AthenzDomain public class AthensDomain extends Identifier { public AthensDomain(String id) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ApplicationAction.java index 8614414dc95..cb5731164c8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ApplicationAction.java @@ -1,8 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athens; /** - * @author bjorncs + * @author mpolden */ public enum ApplicationAction { deploy("deployer"), diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java new file mode 100644 index 00000000000..0635fb850b7 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/Athens.java @@ -0,0 +1,23 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens; + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; +import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; + +/** + * Interface for integrating controller with Athens. + * + * @author mpolden + */ +public interface Athens { + + String principalTokenHeader(); + AthensPrincipal principalFrom(ScrewdriverId screwdriverId); + AthensPrincipal principalFrom(UserId userId); + NTokenValidator validator(); + NToken nTokenFrom(String rawToken); + ZmsClientFactory zmsClientFactory(); + AthensDomain screwdriverDomain(); + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPrincipal.java index 03d9f60c6b0..58b878870b9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPrincipal.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athens; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; @@ -10,12 +10,12 @@ import java.util.Objects; /** * @author bjorncs */ -public class AthenzPrincipal implements Principal { +public class AthensPrincipal implements Principal { private final AthensDomain domain; private final UserId userId; - public AthenzPrincipal(AthensDomain domain, UserId userId) { + public AthensPrincipal(AthensDomain domain, UserId userId) { this.domain = domain; this.userId = userId; } @@ -33,23 +33,15 @@ public class AthenzPrincipal implements Principal { } @Override - public String getName() { - return userId.id(); - } - - @Override public String toString() { - return "AthenzPrincipal{" + - "domain=" + domain + - ", userId=" + userId + - '}'; + return toYRN(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AthenzPrincipal that = (AthenzPrincipal) o; + AthensPrincipal that = (AthensPrincipal) o; return Objects.equals(domain, that.domain) && Objects.equals(userId, that.userId); } @@ -59,4 +51,9 @@ public class AthenzPrincipal implements Principal { return Objects.hash(domain, userId); } + @Override + public String getName() { + return userId.id(); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPublicKey.java index 01596ead0f4..9bbb5f28d8f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensPublicKey.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athens; import java.security.PublicKey; import java.util.Objects; @@ -7,12 +7,11 @@ import java.util.Objects; /** * @author bjorncs */ -public class AthenzPublicKey { - +public class AthensPublicKey { private final PublicKey publicKey; private final String keyId; - public AthenzPublicKey(PublicKey publicKey, String keyId) { + public AthensPublicKey(PublicKey publicKey, String keyId) { this.publicKey = publicKey; this.keyId = keyId; } @@ -29,7 +28,7 @@ public class AthenzPublicKey { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AthenzPublicKey that = (AthenzPublicKey) o; + AthensPublicKey that = (AthensPublicKey) o; return Objects.equals(publicKey, that.publicKey) && Objects.equals(keyId, that.keyId); } @@ -41,7 +40,7 @@ public class AthenzPublicKey { @Override public String toString() { - return "AthenzPublicKey{" + + return "AthensPublicKey{" + "publicKey=" + publicKey + ", keyId='" + keyId + '\'' + '}'; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensService.java index 780a14e4446..42af966be3d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/AthensService.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athens; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; @@ -8,20 +8,16 @@ import java.util.Objects; /** * @author bjorncs */ -public class AthenzService { +public class AthensService { private final AthensDomain domain; private final String serviceName; - public AthenzService(AthensDomain domain, String serviceName) { + public AthensService(AthensDomain domain, String serviceName) { this.domain = domain; this.serviceName = serviceName; } - public AthenzService(String domain, String serviceName) { - this(new AthensDomain(domain), serviceName); - } - public String toFullServiceName() { return domain.id() + "." + serviceName; } @@ -38,7 +34,7 @@ public class AthenzService { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AthenzService that = (AthenzService) o; + AthensService that = (AthensService) o; return Objects.equals(domain, that.domain) && Objects.equals(serviceName, that.serviceName); } @@ -50,6 +46,6 @@ public class AthenzService { @Override public String toString() { - return String.format("AthenzService(%s)", toFullServiceName()); + return String.format("AthensService(%s)", toFullServiceName()); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/InvalidTokenException.java index e41bd8d4283..9c21d5814cb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/InvalidTokenException.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athens; /** * @author bjorncs diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java new file mode 100644 index 00000000000..b74872b4c6a --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NToken.java @@ -0,0 +1,21 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens; + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; + +import java.security.PublicKey; + +/** + * @author mpolden + */ +public interface NToken { + + AthensPrincipal getPrincipal(); + UserId getUser(); + AthensDomain getDomain(); + String getToken(); + String getKeyId(); + void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException; + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java new file mode 100644 index 00000000000..905d7d864a3 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/NTokenValidator.java @@ -0,0 +1,12 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens; + +/** + * @author mpolden + */ +public interface NTokenValidator { + + void preloadPublicKeys(); + AthensPrincipal validate(NToken nToken) throws InvalidTokenException; + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClient.java index 274a8fdf438..7ff54957e16 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClient.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athens; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; @@ -10,7 +10,6 @@ import java.util.List; * @author bjorncs */ public interface ZmsClient { - void createTenant(AthensDomain tenantDomain); void deleteTenant(AthensDomain tenantDomain); @@ -19,17 +18,18 @@ public interface ZmsClient { void deleteApplication(AthensDomain tenantDomain, ApplicationId applicationName); - boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName); + boolean hasApplicationAccess(AthensPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName); - boolean hasTenantAdminAccess(AthenzPrincipal principal, AthensDomain tenantDomain); + boolean hasTenantAdminAccess(AthensPrincipal principal, AthensDomain tenantDomain); // Used before vespa tenancy is established for the domain. - boolean isDomainAdmin(AthenzPrincipal principal, AthensDomain domain); + boolean isDomainAdmin(AthensPrincipal principal, AthensDomain domain); List<AthensDomain> getDomainList(String prefix); - AthenzPublicKey getPublicKey(AthenzService service, String keyId); + List<AthensDomain> getTenantDomainsForUser(AthensPrincipal principal); - List<AthenzPublicKey> getPublicKeys(AthenzService service); + AthensPublicKey getPublicKey(AthensService service, String keyId); + List<AthensPublicKey> getPublicKeys(AthensService service); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java new file mode 100644 index 00000000000..e00f3ce2f64 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsClientFactory.java @@ -0,0 +1,11 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens; + +/** + * @author bjorncs + */ +public interface ZmsClientFactory { + ZmsClient createClientWithServicePrincipal(); + + ZmsClient createClientWithAuthorizedServiceToken(NToken authorizedServiceToken); +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsException.java index 59548339d11..ed5b2daca86 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsException.java @@ -1,7 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.athenz.zms.ZMSClientException; +package com.yahoo.vespa.hosted.controller.api.integration.athens; /** * @author bjorncs @@ -10,11 +8,14 @@ public class ZmsException extends RuntimeException { private final int code; - public ZmsException(ZMSClientException e) { - super(e.getMessage(), e); - this.code = e.getCode(); + public ZmsException(Throwable t, int code) { + super(t.getMessage(), t); + this.code = code; } + public ZmsException(int code) { + this.code = code; + } public int getCode() { return code; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java new file mode 100644 index 00000000000..4f8e5f5ff05 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/ZmsKeystore.java @@ -0,0 +1,19 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens; + +import java.security.PublicKey; +import java.util.Optional; + +/** + * Interface for a keystore containing public keys for Athens services + * + * @author bjorncs + */ +@FunctionalInterface +public interface ZmsKeystore { + Optional<PublicKey> getPublicKey(AthensService service, String keyId); + + default void preloadKeys(AthensService service) { + // Default implementation is noop + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthensDbMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensDbMock.java index 55fe435c9be..8a02d0dcff5 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthensDbMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensDbMock.java @@ -1,10 +1,10 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.mock; +package com.yahoo.vespa.hosted.controller.api.integration.athens.mock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ApplicationAction; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; import java.util.HashMap; import java.util.HashSet; @@ -26,8 +26,8 @@ public class AthensDbMock { public static class Domain { public final AthensDomain name; - public final Set<AthenzPrincipal> admins = new HashSet<>(); - public final Set<AthenzPrincipal> tenantAdmins = new HashSet<>(); + public final Set<AthensPrincipal> admins = new HashSet<>(); + public final Set<AthensPrincipal> tenantAdmins = new HashSet<>(); public final Map<ApplicationId, Application> applications = new HashMap<>(); public boolean isVespaTenant = false; @@ -35,12 +35,12 @@ public class AthensDbMock { this.name = name; } - public Domain admin(AthenzPrincipal user) { + public Domain admin(AthensPrincipal user) { admins.add(user); return this; } - public Domain tenantAdmin(AthenzPrincipal user) { + public Domain tenantAdmin(AthensPrincipal user) { tenantAdmins.add(user); return this; } @@ -56,7 +56,7 @@ public class AthensDbMock { public static class Application { - public final Map<ApplicationAction, Set<AthenzPrincipal>> acl = new HashMap<>(); + public final Map<ApplicationAction, Set<AthensPrincipal>> acl = new HashMap<>(); public Application() { acl.put(ApplicationAction.deploy, new HashSet<>()); @@ -64,7 +64,7 @@ public class AthensDbMock { acl.put(ApplicationAction.write, new HashSet<>()); } - public Application addRoleMember(ApplicationAction action, AthenzPrincipal user) { + public Application addRoleMember(ApplicationAction action, AthensPrincipal user) { acl.get(action).add(user); return this; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java new file mode 100644 index 00000000000..8bed95bfcd4 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/AthensMock.java @@ -0,0 +1,87 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens.mock; + +import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; +import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; +import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.InvalidTokenException; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NTokenValidator; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory; + +/** + * @author mpolden + */ +public class AthensMock extends AbstractComponent implements Athens { + + private static final AthensDomain userDomain = new AthensDomain("domain1"); + private static final AthensDomain screwdriverDomain = new AthensDomain("screwdriver-domain"); + + private final ZmsClientFactory zmsClientFactory; + private final NTokenValidator nTokenValidator; + + public AthensMock(AthensDbMock athensDb, NTokenValidator nTokenValidator) { + this.zmsClientFactory = new ZmsClientFactoryMock(athensDb); + this.nTokenValidator = nTokenValidator; + } + + public AthensMock(AthensDbMock athensDbMock) { + this(athensDbMock, mockValidator); + } + + @Inject + public AthensMock() { + this(new AthensDbMock(), mockValidator); + } + + @Override + public String principalTokenHeader() { + return "X-Athens-Token"; + } + + @Override + public AthensPrincipal principalFrom(ScrewdriverId screwdriverId) { + return new AthensPrincipal(screwdriverDomain, new UserId("screwdriver-" + screwdriverId.id())); + } + + @Override + public AthensPrincipal principalFrom(UserId userId) { + return new AthensPrincipal(userDomain, userId); + } + + @Override + public NTokenValidator validator() { + return nTokenValidator; + } + + @Override + public NToken nTokenFrom(String rawToken) { + return new NTokenMock(rawToken); + } + + @Override + public ZmsClientFactory zmsClientFactory() { + return zmsClientFactory; + } + + @Override + public AthensDomain screwdriverDomain() { + return screwdriverDomain; + } + + private static final NTokenValidator mockValidator = new NTokenValidator() { + @Override + public void preloadPublicKeys() { + } + + @Override + public AthensPrincipal validate(NToken nToken) throws InvalidTokenException { + return nToken.getPrincipal(); + } + }; + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java new file mode 100644 index 00000000000..ae23a69e409 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/NTokenMock.java @@ -0,0 +1,68 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens.mock; + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.InvalidTokenException; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; + +import java.security.PublicKey; +import java.util.Objects; + +/** + * @author mpolden + */ +public class NTokenMock implements NToken { + + private static final AthensDomain domain = new AthensDomain("test"); + private static final UserId userId = new UserId("user"); + + private final String rawToken; + + public NTokenMock(String rawToken) { + this.rawToken = rawToken; + } + + @Override + public AthensPrincipal getPrincipal() { + return new AthensPrincipal(domain, userId); + } + + @Override + public UserId getUser() { + return userId; + } + + @Override + public AthensDomain getDomain() { + return domain; + } + + @Override + public String getToken() { + return "test-token"; + } + + @Override + public String getKeyId() { + return "test-key"; + } + + @Override + public void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException { + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NTokenMock)) return false; + NTokenMock that = (NTokenMock) o; + return Objects.equals(rawToken, that.rawToken); + } + + @Override + public int hashCode() { + return Objects.hash(rawToken); + } +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java new file mode 100644 index 00000000000..8bc8b29fb4c --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientFactoryMock.java @@ -0,0 +1,49 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athens.mock; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author bjorncs + */ +public class ZmsClientFactoryMock extends AbstractComponent implements ZmsClientFactory { + + private static final Logger log = Logger.getLogger(ZmsClientFactoryMock.class.getName()); + + private final AthensDbMock athens; + + public ZmsClientFactoryMock() { + this(new AthensDbMock()); + } + + ZmsClientFactoryMock(AthensDbMock athens) { + this.athens = athens; + } + + public AthensDbMock getSetup() { + return athens; + } + + @Override + public ZmsClient createClientWithServicePrincipal() { + log("createClientWithServicePrincipal()"); + return new ZmsClientMock(athens); + } + + @Override + public ZmsClient createClientWithAuthorizedServiceToken(NToken authorizedServiceToken) { + log("createClientWithAuthorizedServiceToken(authorizedServiceToken='%s')", authorizedServiceToken); + return new ZmsClientMock(athens); + } + + private static void log(String format, Object... args) { + log.log(Level.INFO, String.format(format, args)); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientMock.java index bba7d410bf7..97f391f792d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/ZmsClientMock.java @@ -1,15 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.mock; +package com.yahoo.vespa.hosted.controller.api.integration.athens.mock; -import com.yahoo.athenz.zms.ZMSClientException; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPublicKey; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensService; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException; import java.util.ArrayList; import java.util.List; @@ -17,6 +16,8 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; +import static java.util.stream.Collectors.toList; + /** * @author bjorncs */ @@ -61,7 +62,7 @@ public class ZmsClientMock implements ZmsClient { } @Override - public boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName) { + public boolean hasApplicationAccess(AthensPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName) { log("hasApplicationAccess(principal='%s', action='%s', tenantDomain='%s', applicationName='%s')", principal, action, tenantDomain, applicationName); AthensDbMock.Domain domain = getDomainOrThrow(tenantDomain, true); @@ -73,14 +74,14 @@ public class ZmsClientMock implements ZmsClient { } @Override - public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthensDomain tenantDomain) { + public boolean hasTenantAdminAccess(AthensPrincipal principal, AthensDomain tenantDomain) { log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", principal, tenantDomain); return isDomainAdmin(principal, tenantDomain) || getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(principal); } @Override - public boolean isDomainAdmin(AthenzPrincipal principal, AthensDomain domain) { + public boolean isDomainAdmin(AthensPrincipal principal, AthensDomain domain) { log("isDomainAdmin(principal='%s', domain='%s')", principal, domain); return getDomainOrThrow(domain, false).admins.contains(principal); } @@ -92,12 +93,21 @@ public class ZmsClientMock implements ZmsClient { } @Override - public AthenzPublicKey getPublicKey(AthenzService service, String keyId) { + public List<AthensDomain> getTenantDomainsForUser(AthensPrincipal principal) { + log("getTenantDomainsForUser(principal='%s')", principal); + return athens.domains.values().stream() + .filter(domain -> domain.tenantAdmins.contains(principal) || domain.admins.contains(principal)) + .map(domain -> domain.name) + .collect(toList()); + } + + @Override + public AthensPublicKey getPublicKey(AthensService service, String keyId) { throw new UnsupportedOperationException(); } @Override - public List<AthenzPublicKey> getPublicKeys(AthenzService service) { + public List<AthensPublicKey> getPublicKeys(AthensService service) { throw new UnsupportedOperationException(); } @@ -111,7 +121,7 @@ public class ZmsClientMock implements ZmsClient { } private static ZmsException zmsException(int code, String message, Object... args) { - return new ZmsException(new ZMSClientException(code, String.format(message, args))); + return new ZmsException(new RuntimeException(String.format(message, args)), code); } private static void log(String format, Object... args) { diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java new file mode 100644 index 00000000000..d4454503786 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/mock/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.vespa.hosted.controller.api.integration.athens.mock; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java new file mode 100644 index 00000000000..eabe214abf2 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athens/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.vespa.hosted.controller.api.integration.athens; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/controller-server/pom.xml b/controller-server/pom.xml index 2e706f6df24..fa21098a092 100644 --- a/controller-server/pom.xml +++ b/controller-server/pom.xml @@ -92,13 +92,6 @@ <scope>provided</scope> </dependency> - <!-- required for Athenz libraries --> - <dependency> - <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk15on</artifactId> - <scope>provided</scope> - </dependency> - <!-- compile --> <dependency> @@ -124,49 +117,6 @@ <version>1.6</version> </dependency> - <dependency> - <groupId>com.yahoo.athenz</groupId> - <artifactId>athenz-zms-java-client</artifactId> - <scope>compile</scope> - <exclusions> - <exclusion> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </exclusion> - <!-- Exclude all Jersey bundles provided by JDisc --> - <exclusion> - <groupId>org.glassfish.jersey.core</groupId> - <artifactId>jersey-client</artifactId> - </exclusion> - <exclusion> - <groupId>org.glassfish.jersey.media</groupId> - <artifactId>jersey-media-json-jackson</artifactId> - </exclusion> - <!-- BouncyCastle is not bundled due to class loading issues - when security provider is registered from inside a OSGi bundle --> - <exclusion> - <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk15on</artifactId> - </exclusion> - <exclusion> - <groupId>org.bouncycastle</groupId> - <artifactId>bcprov-jdk15on</artifactId> - </exclusion> - </exclusions> - </dependency> - - <dependency> - <groupId>com.yahoo.athenz</groupId> - <artifactId>athenz-zts-java-client</artifactId> - <scope>compile</scope> - <exclusions> - <exclusion> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </exclusion> - </exclusions> - </dependency> - <!-- test --> <dependency> diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 7512e14643f..ac2d0373d9c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -22,6 +22,10 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.identifiers.RevisionId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log; import com.yahoo.vespa.hosted.controller.api.integration.configserver.NoInstanceException; @@ -40,10 +44,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger; import com.yahoo.vespa.hosted.controller.maintenance.DeploymentExpirer; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; @@ -84,7 +84,7 @@ public class ApplicationController { private final CuratorDb curator; private final RotationRepository rotationRepository; - private final AthenzClientFactory zmsClientFactory; + private final ZmsClientFactory zmsClientFactory; private final NameService nameService; private final ConfigServerClient configserverClient; private final RoutingGenerator routingGenerator; @@ -94,7 +94,7 @@ public class ApplicationController { ApplicationController(Controller controller, ControllerDb db, CuratorDb curator, RotationRepository rotationRepository, - AthenzClientFactory zmsClientFactory, + ZmsClientFactory zmsClientFactory, NameService nameService, ConfigServerClient configserverClient, RoutingGenerator routingGenerator, Clock clock) { this.controller = controller; @@ -249,7 +249,7 @@ public class ApplicationController { if (tenant.get().isAthensTenant() && ! token.isPresent()) throw new IllegalArgumentException("Could not create '" + id + "': No NToken provided"); if (tenant.get().isAthensTenant()) { - ZmsClient zmsClient = zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()); + ZmsClient zmsClient = zmsClientFactory.createClientWithAuthorizedServiceToken(token.get()); try { zmsClient.deleteApplication(tenant.get().getAthensDomain().get(), new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); @@ -490,7 +490,7 @@ public class ApplicationController { // NB: Next 2 lines should have been one transaction if (tenant.isAthensTenant()) - zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()) + zmsClientFactory.createClientWithAuthorizedServiceToken(token.get()) .deleteApplication(tenant.getAthensDomain().get(), new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); db.deleteApplication(id); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index e338bc17788..35551b50953 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -15,6 +15,8 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient; import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; @@ -25,8 +27,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingSe import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; @@ -73,6 +73,7 @@ public class Controller extends AbstractComponent { private final ConfigServerClient configServerClient; private final MetricsService metricsService; private final Chef chefClient; + private final Athens athens; private final ZmsClient zmsClient; /** @@ -87,11 +88,11 @@ public class Controller extends AbstractComponent { GlobalRoutingService globalRoutingService, ZoneRegistry zoneRegistry, ConfigServerClient configServerClient, MetricsService metricsService, NameService nameService, - RoutingGenerator routingGenerator, Chef chefClient, AthenzClientFactory athenzClientFactory) { + RoutingGenerator routingGenerator, Chef chefClient, Athens athens) { this(db, curator, rotationRepository, gitHub, jiraClient, entityService, globalRoutingService, zoneRegistry, configServerClient, metricsService, nameService, routingGenerator, chefClient, - Clock.systemUTC(), athenzClientFactory); + Clock.systemUTC(), athens); } public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository, @@ -99,8 +100,7 @@ public class Controller extends AbstractComponent { GlobalRoutingService globalRoutingService, ZoneRegistry zoneRegistry, ConfigServerClient configServerClient, MetricsService metricsService, NameService nameService, - RoutingGenerator routingGenerator, Chef chefClient, Clock clock, - AthenzClientFactory athenzClientFactory) { + RoutingGenerator routingGenerator, Chef chefClient, Clock clock, Athens athens) { Objects.requireNonNull(db, "Controller db cannot be null"); Objects.requireNonNull(curator, "Curator cannot be null"); Objects.requireNonNull(rotationRepository, "Rotation repository cannot be null"); @@ -115,7 +115,7 @@ public class Controller extends AbstractComponent { Objects.requireNonNull(routingGenerator, "RoutingGenerator cannot be null"); Objects.requireNonNull(chefClient, "ChefClient cannot be null"); Objects.requireNonNull(clock, "Clock cannot be null"); - Objects.requireNonNull(athenzClientFactory, "Athens cannot be null"); + Objects.requireNonNull(athens, "Athens cannot be null"); this.rotationRepository = rotationRepository; this.curator = curator; @@ -127,11 +127,12 @@ public class Controller extends AbstractComponent { this.metricsService = metricsService; this.chefClient = chefClient; this.clock = clock; - this.zmsClient = athenzClientFactory.createZmsClientWithServicePrincipal(); + this.athens = athens; + this.zmsClient = athens.zmsClientFactory().createClientWithServicePrincipal(); - applicationController = new ApplicationController(this, db, curator, rotationRepository, athenzClientFactory, + applicationController = new ApplicationController(this, db, curator, rotationRepository, athens.zmsClientFactory(), nameService, configServerClient, routingGenerator, clock); - tenantController = new TenantController(this, db, curator, entityService, athenzClientFactory); + tenantController = new TenantController(this, db, curator, entityService); } /** Returns the instance controlling tenants */ @@ -144,6 +145,10 @@ public class Controller extends AbstractComponent { return zmsClient.getDomainList(prefix); } + public Athens athens() { + return athens; + } + /** * Fetch list of all active OpsDB properties. * diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java index 229c46f0a22..fafd0b04dd2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java @@ -11,12 +11,11 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.persistence.PersistenceException; @@ -48,15 +47,14 @@ public class TenantController { /** For working memory storage and sharing between controllers */ private final CuratorDb curator; - private final AthenzClientFactory athenzClientFactory; + private final ZmsClientFactory zmsClientFactory; private final EntityService entityService; - public TenantController(Controller controller, ControllerDb db, CuratorDb curator, EntityService entityService, - AthenzClientFactory athenzClientFactory) { + public TenantController(Controller controller, ControllerDb db, CuratorDb curator, EntityService entityService) { this.controller = controller; this.db = db; this.curator = curator; - this.athenzClientFactory = athenzClientFactory; + this.zmsClientFactory = controller.athens().zmsClientFactory(); this.entityService = entityService; } @@ -66,8 +64,8 @@ public class TenantController { public List<Tenant> asList(UserId user) { Set<UserGroup> userGroups = entityService.getUserGroups(user); - Set<AthensDomain> userDomains = new HashSet<>(athenzClientFactory.createZtsClientWithServicePrincipal() - .getTenantDomainsForUser(AthenzUtils.createPrincipal(user))); + Set<AthensDomain> userDomains = new HashSet<>(zmsClientFactory.createClientWithServicePrincipal() + .getTenantDomainsForUser(controller.athens().principalFrom(user))); Predicate<Tenant> hasUsersGroup = (tenant) -> tenant.getUserGroup().isPresent() && userGroups.contains(tenant.getUserGroup().get()); Predicate<Tenant> hasUsersDomain = (tenant) -> tenant.getAthensDomain().isPresent() && userDomains.contains(tenant.getAthensDomain().get()); @@ -110,7 +108,7 @@ public class TenantController { if (existingTenantWithDomain.isPresent()) throw new IllegalArgumentException("Could not create " + tenant + ": The Athens domain '" + domain + "' is already connected to " + existingTenantWithDomain.get()); - ZmsClient zmsClient = athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()); + ZmsClient zmsClient = zmsClientFactory.createClientWithAuthorizedServiceToken(token.get()); try { zmsClient.deleteTenant(domain); } catch (ZmsException ignored) { } zmsClient.createTenant(domain); } @@ -160,7 +158,7 @@ public class TenantController { throw new IllegalArgumentException("Could not set domain of " + updatedTenant + " to '" + newDomain + "':" + existingTenantWithNewDomain.get() + " already has this domain"); - ZmsClient zmsClient = athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()); + ZmsClient zmsClient = zmsClientFactory.createClientWithAuthorizedServiceToken(token.get()); zmsClient.createTenant(newDomain); List<Application> applications = controller.applications().asList(TenantName.from(existingTenant.getId().id())); applications.forEach(a -> zmsClient.addApplication(newDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value()))); @@ -186,8 +184,7 @@ public class TenantController { throw new RuntimeException(e); } if (tenant.isAthensTenant()) - athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()) - .deleteTenant(tenant.getAthensDomain().get()); + zmsClientFactory.createClientWithAuthorizedServiceToken(token.get()).deleteTenant(tenant.getAthensDomain().get()); log.info("Deleted " + tenant); } } @@ -207,7 +204,7 @@ public class TenantController { throw new IllegalArgumentException("Could not migrate " + existing + " to " + tenantDomain + ": " + "Tenant is not currently an OpsDb tenant"); - ZmsClient zmsClient = athenzClientFactory.createZmsClientWithAuthorizedServiceToken(nToken); + ZmsClient zmsClient = zmsClientFactory.createClientWithAuthorizedServiceToken(nToken); zmsClient.createTenant(tenantDomain); List<Application> applications = controller.applications().asList(TenantName.from(existing.getId().id())); applications.forEach(a -> { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java deleted file mode 100644 index b6a21f94f74..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -/** - * @author bjorncs - */ -public interface AthenzClientFactory { - - ZmsClient createZmsClientWithServicePrincipal(); - - ZtsClient createZtsClientWithServicePrincipal(); - - ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken); - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java deleted file mode 100644 index 0c0f4729100..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; - -/** - * @author bjorncs - */ -public class AthenzUtils { - - private AthenzUtils() {} - - // TODO Change to "user" as primary user principal domain. Also support "yby" for a limited time as per recent Athenz changes - public static final AthensDomain USER_PRINCIPAL_DOMAIN = new AthensDomain("yby"); - public static final AthensDomain SCREWDRIVER_DOMAIN = new AthensDomain("cd.screwdriver.project"); - public static final AthenzService ZMS_ATHENZ_SERVICE = new AthenzService("sys.auth", "zms"); - - public static AthenzPrincipal createPrincipal(UserId userId) { - return new AthenzPrincipal(USER_PRINCIPAL_DOMAIN, userId); - } - - public static AthenzPrincipal createPrincipal(ScrewdriverId screwdriverId) { - return new AthenzPrincipal(SCREWDRIVER_DOMAIN, new UserId("sd" + screwdriverId.id())); - } - - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java deleted file mode 100644 index fec0523aaab..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.athenz.auth.token.PrincipalToken; -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; - -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.concurrent.TimeUnit; - -/** - * Represents an Athenz NToken (principal token) - * - * @author bjorncs - */ -// TODO Split out encoding/decoding of token into separate class. Move NToken to controller-api. -public class NToken { - - // Max allowed skew in token timestamp (only for creation, not expiry timestamp) - private static final int ALLOWED_TIMESTAMP_OFFSET = (int) TimeUnit.SECONDS.toSeconds(300); - - private final PrincipalToken token; - - // Note: PrincipalToken does not provide any way of constructing an instance from a unsigned token string - public NToken(String signedToken) { - try { - this.token = new PrincipalToken(signedToken); - if (this.token.getSignature() == null) { - throw new IllegalArgumentException("Signature missing (unsigned token)"); - } - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Malformed NToken: " + e.getMessage()); - } - } - - public AthenzPrincipal getPrincipal() { - return new AthenzPrincipal(getDomain(), getUser()); - } - - public UserId getUser() { - return new UserId(token.getName()); - } - - public AthensDomain getDomain() { - return new AthensDomain(token.getDomain()); - } - - public String getToken() { - return token.getSignedToken(); - } - - public String getKeyId() { - return token.getKeyId(); - } - - public void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException { - StringBuilder errorMessageBuilder = new StringBuilder(); - if (!token.validate(publicKey, ALLOWED_TIMESTAMP_OFFSET, true, errorMessageBuilder)) { - throw new InvalidTokenException("NToken is expired or has invalid signature: " + errorMessageBuilder.toString()); - } - } - - @Override - public String toString() { - return String.format("NToken(%s)", getToken()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NToken nToken = (NToken) o; - return Objects.equals(getToken(), nToken.getToken()); // PrincipalToken does not implement equals() - } - - @Override - public int hashCode() { - return Objects.hash(getToken()); // PrincipalToken does not implement hashcode() - } - - public static class Builder { - - private final String version; - private final AthenzPrincipal principal; - private final PrivateKey privateKey; - private final String keyId; - private Optional<String> salt = Optional.empty(); - private Optional<String> hostname = Optional.empty(); - private Optional<String> ip = Optional.empty(); - private OptionalLong issueTime = OptionalLong.empty(); - private OptionalLong expirationWindow = OptionalLong.empty(); - - /** - * NOTE: We must have some signature, else we might end up with problems later on as - * {@link PrincipalToken#PrincipalToken(String)} only accepts signed token - * (supplying an unsigned token to the constructor will result in inconsistent state) - */ - public Builder(String version, AthenzPrincipal principal, PrivateKey privateKey, String keyId) { - this.version = version; - this.principal = principal; - this.privateKey = privateKey; - this.keyId = keyId; - } - - public Builder salt(String salt) { - this.salt = Optional.of(salt); - return this; - } - - public Builder hostname(String hostname) { - this.hostname = Optional.of(hostname); - return this; - } - - public Builder ip(String ip) { - this.ip = Optional.of(ip); - return this; - } - - public Builder issueTime(long issueTime) { - this.issueTime = OptionalLong.of(issueTime); - return this; - } - - public Builder expirationWindow(long expirationWindow) { - this.expirationWindow = OptionalLong.of(expirationWindow); - return this; - } - - public NToken build() { - PrincipalToken token = new PrincipalToken.Builder(version, principal.getDomain().id(), principal.getName()) - .keyId(this.keyId) - .salt(this.salt.orElse(null)) - .host(this.hostname.orElse(null)) - .ip(this.ip.orElse(null)) - .issueTime(this.issueTime.orElse(0)) - .expirationWindow(this.expirationWindow.orElse(0)) - .build(); - token.sign(this.privateKey); - return new NToken(token.getSignedToken()); - } - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java deleted file mode 100644 index 93fed95c768..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import java.security.PublicKey; -import java.util.Optional; - -/** - * @author bjorncs - */ -public interface ZmsKeystore { - - Optional<PublicKey> getPublicKey(AthenzService service, String keyId); - - default void preloadKeys(AthenzService service) { /* Default implementation is noop */ } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java deleted file mode 100644 index a44f1af0d2a..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; - -import java.util.List; - -/** - * @author bjorncs - */ -public interface ZtsClient { - - List<AthensDomain> getTenantDomainsForUser(AthenzPrincipal principal); - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java deleted file mode 100644 index cb0b21ba459..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.athenz.zts.ZTSClientException; - -/** - * @author bjorncs - */ -public class ZtsException extends RuntimeException { - - private final int code; - - public ZtsException(ZTSClientException e) { - super(e.getMessage(), e); - this.code = e.getCode(); - } - - - public int getCode() { - return code; - } -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java deleted file mode 100644 index de771ff2e17..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.filter; - -import com.google.inject.Inject; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.FastContentWriter; -import com.yahoo.jdisc.handler.ResponseDispatch; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.filter.DiscFilterRequest; -import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import com.yahoo.jdisc.http.server.jetty.ErrorResponseContentCreator; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; -import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; - -import java.util.Optional; -import java.util.concurrent.Executor; - -/** - * Performs authentication by validating the principal token (NToken) header. - * - * @author bjorncs - */ -public class AthenzPrincipalFilter implements SecurityRequestFilter { - - private final ErrorResponseContentCreator responseCreator = new ErrorResponseContentCreator(); - private final NTokenValidator validator; - private final String principalTokenHeader; - - /** - * @param executor to preload the ZMS public keys with - */ - @Inject - public AthenzPrincipalFilter(ZmsKeystore zmsKeystore, Executor executor, AthenzConfig config) { - this(new NTokenValidator(zmsKeystore), executor, config.principalHeaderName()); - } - - AthenzPrincipalFilter(NTokenValidator validator, Executor executor, String principalTokenHeader) { - this.validator = validator; - this.principalTokenHeader = principalTokenHeader; - executor.execute(validator::preloadPublicKeys); - } - - @Override - public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { - String rawToken = request.getHeader(principalTokenHeader); - if (rawToken == null || rawToken.isEmpty()) { - sendUnauthorized(request, responseHandler, "NToken is missing"); - return; - } - try { - AthenzPrincipal principal = validator.validate(new NToken(rawToken)); - request.setUserPrincipal(principal); - request.setRemoteUser(principal.getName()); - } catch (InvalidTokenException e) { - sendUnauthorized(request, responseHandler, e.getMessage()); - } - } - - private void sendUnauthorized(DiscFilterRequest request, ResponseHandler responseHandler, String message) { - try (FastContentWriter writer = ResponseDispatch.newInstance(Response.Status.UNAUTHORIZED) - .connectFastWriter(responseHandler)) { - writer.write( - responseCreator.createErrorContent( - request.getRequestURI(), Response.Status.UNAUTHORIZED, Optional.of(message))); - } - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java deleted file mode 100644 index f43d2d8e80e..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.filter; - -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; - -import java.security.PublicKey; -import java.util.Optional; -import java.util.logging.Logger; - -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; - -/** - * Validates the content of an NToken: - * 1) Verifies that the token is signed by the sys.auth.zms service (by validating the signature) - * 2) Verifies that the token is not expired - * - * @author bjorncs - */ -class NTokenValidator { - - private static final Logger log = Logger.getLogger(NTokenValidator.class.getName()); - - private final ZmsKeystore keystore; - - NTokenValidator(ZmsKeystore keystore) { - this.keystore = keystore; - } - - void preloadPublicKeys() { - keystore.preloadKeys(ZMS_ATHENZ_SERVICE); - } - - AthenzPrincipal validate(NToken token) throws InvalidTokenException { - PublicKey zmsPublicKey = getPublicKey(token.getKeyId()) - .orElseThrow(() -> new InvalidTokenException("NToken has an unknown keyId")); - validateSignatureAndExpiration(token, zmsPublicKey); - return token.getPrincipal(); - } - - private Optional<PublicKey> getPublicKey(String keyId) throws InvalidTokenException { - try { - return keystore.getPublicKey(ZMS_ATHENZ_SERVICE, keyId); - } catch (Exception e) { - logDebug(e.getMessage()); - throw new InvalidTokenException("Failed to retrieve public key"); - } - } - - private static void validateSignatureAndExpiration(NToken token, PublicKey zmsPublicKey) throws InvalidTokenException { - try { - token.validateSignatureAndExpiration(zmsPublicKey); - } catch (InvalidTokenException e) { - // The underlying error message is not user friendly - logDebug(e.getMessage()); - throw new InvalidTokenException("NToken is expired or has invalid signature"); - } - } - - private static void logDebug(String message) { - log.log(LogLevel.DEBUG, "Failed to validate NToken: " + message); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java deleted file mode 100644 index 292841d89f3..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.impl; - -import com.google.inject.Inject; -import com.yahoo.athenz.auth.Principal; -import com.yahoo.athenz.auth.impl.PrincipalAuthority; -import com.yahoo.athenz.auth.impl.SimplePrincipal; -import com.yahoo.athenz.auth.impl.SimpleServiceIdentityProvider; -import com.yahoo.athenz.auth.token.PrincipalToken; -import com.yahoo.athenz.auth.util.Crypto; -import com.yahoo.athenz.zms.ZMSClient; -import com.yahoo.athenz.zts.ZTSClient; -import com.yahoo.vespa.hosted.controller.api.integration.security.KeyService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; -import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; - -import java.security.PrivateKey; -import java.util.concurrent.TimeUnit; - -/** - * @author bjorncs - */ -public class AthenzClientFactoryImpl implements AthenzClientFactory { - - private final KeyService secretService; - private final AthenzConfig config; - private final AthenzPrincipalAuthority athenzPrincipalAuthority; - - @Inject - public AthenzClientFactoryImpl(KeyService secretService, AthenzConfig config) { - this.secretService = secretService; - this.config = config; - this.athenzPrincipalAuthority = new AthenzPrincipalAuthority(config.principalHeaderName()); - } - - /** - * @return A ZMS client instance with the service identity as principal. - */ - @Override - public ZmsClient createZmsClientWithServicePrincipal() { - return new ZmsClientImpl(new ZMSClient(config.zmsUrl(), createServicePrincipal()), config); - } - - /** - * @return A ZTS client instance with the service identity as principal. - */ - @Override - public ZtsClient createZtsClientWithServicePrincipal() { - return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), config); - } - - /** - * @return A ZMS client created with a dual principal representing both the tenant admin and the service identity. - */ - @Override - public ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken) { - PrincipalToken signedToken = new PrincipalToken(authorizedServiceToken.getToken()); - AthenzConfig.Service service = config.service(); - signedToken.signForAuthorizedService( - config.domain() + "." + service.name(), service.publicKeyId(), getServicePrivateKey()); - - Principal dualPrincipal = SimplePrincipal.create( - "yby", signedToken.getName(), signedToken.getSignedToken(), athenzPrincipalAuthority); - return new ZmsClientImpl(new ZMSClient(config.zmsUrl(), dualPrincipal), config); - - } - - private Principal createServicePrincipal() { - AthenzConfig.Service service = config.service(); - // TODO bjorncs: Cache principal token - SimpleServiceIdentityProvider identityProvider = - new SimpleServiceIdentityProvider( - athenzPrincipalAuthority, config.domain(), service.name(), - getServicePrivateKey(), service.publicKeyId(), /*tokenTimeout*/TimeUnit.HOURS.toSeconds(1)); - return identityProvider.getIdentity(config.domain(), service.name()); - } - - private PrivateKey getServicePrivateKey() { - AthenzConfig.Service service = config.service(); - String privateKey = secretService.getSecret(service.privateKeySecretName(), service.privateKeyVersion()).trim(); - return Crypto.loadPrivateKey(privateKey); - } - - private static class AthenzPrincipalAuthority extends PrincipalAuthority { - private final String principalHeaderName; - - public AthenzPrincipalAuthority(String principalHeaderName) { - this.principalHeaderName = principalHeaderName; - } - - @Override - public String getHeader() { - return principalHeaderName; - } - } - - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java deleted file mode 100644 index 48bd8af7dfe..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.impl; - -import com.yahoo.athenz.auth.util.Crypto; -import com.yahoo.athenz.zms.DomainList; -import com.yahoo.athenz.zms.ProviderResourceGroupRoles; -import com.yahoo.athenz.zms.PublicKeyEntry; -import com.yahoo.athenz.zms.ServiceIdentity; -import com.yahoo.athenz.zms.Tenancy; -import com.yahoo.athenz.zms.TenantRoleAction; -import com.yahoo.athenz.zms.ZMSClient; -import com.yahoo.athenz.zms.ZMSClientException; -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; -import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Supplier; -import java.util.logging.Logger; - -import static java.util.stream.Collectors.toList; - -/** - * @author bjorncs - */ -public class ZmsClientImpl implements ZmsClient { - - private static final Logger log = Logger.getLogger(ZmsClientImpl.class.getName()); - private final ZMSClient zmsClient; - private final AthenzService service; - - ZmsClientImpl(ZMSClient zmsClient, AthenzConfig config) { - this.zmsClient = zmsClient; - this.service = new AthenzService(config.domain(), config.service().name()); - } - - @Override - public void createTenant(AthensDomain tenantDomain) { - log("putTenancy(tenantDomain=%s, service=%s)", tenantDomain, service); - runOrThrow(() -> { - Tenancy tenancy = new Tenancy() - .setDomain(tenantDomain.id()) - .setService(service.toFullServiceName()) - .setResourceGroups(Collections.emptyList()); - zmsClient.putTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null, tenancy); - }); - } - - @Override - public void deleteTenant(AthensDomain tenantDomain) { - log("deleteTenancy(tenantDomain=%s, service=%s)", tenantDomain, service); - runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null)); - } - - @Override - public void addApplication(AthensDomain tenantDomain, ApplicationId applicationName) { - List<TenantRoleAction> tenantRoleActions = createTenantRoleActions(); - log("putProviderResourceGroupRoles(" + - "tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)", - tenantDomain, service.getDomain().getName(), service.getServiceName(), applicationName, tenantRoleActions); - runOrThrow(() -> { - ProviderResourceGroupRoles resourceGroupRoles = new ProviderResourceGroupRoles() - .setDomain(service.getDomain().getName()) - .setService(service.getServiceName()) - .setTenant(tenantDomain.id()) - .setResourceGroup(applicationName.id()) - .setRoles(tenantRoleActions); - zmsClient.putProviderResourceGroupRoles( - tenantDomain.id(), service.getDomain().getName(), service.getServiceName(), - applicationName.id(), /*auditref*/null, resourceGroupRoles); - }); - } - - @Override - public void deleteApplication(AthensDomain tenantDomain, ApplicationId applicationName) { - log("deleteProviderResourceGroupRoles(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)", - tenantDomain, service.getDomain().getName(), service.getServiceName(), applicationName); - runOrThrow(() -> { - zmsClient.deleteProviderResourceGroupRoles( - tenantDomain.id(), service.getDomain().getName(), service.getServiceName(), applicationName.id(), /*auditref*/null); - }); - } - - @Override - public boolean hasApplicationAccess( - AthenzPrincipal principal, ApplicationAction action, AthensDomain tenantDomain, ApplicationId applicationName) { - return hasAccess( - action.name(), applicationResourceString(tenantDomain, applicationName), principal); - } - - @Override - public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthensDomain tenantDomain) { - return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), principal); - } - - /** - * Used when creating tenancies. As there are no tenancy policies at this point, - * we cannot use {@link #hasTenantAdminAccess(AthenzPrincipal, AthensDomain)} - */ - @Override - public boolean isDomainAdmin(AthenzPrincipal principal, AthensDomain domain) { - log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", principal); - return getOrThrow( - () -> zmsClient.getMembership(domain.id(), "admin", principal.toYRN()).getIsMember()); - } - - @Override - public List<AthensDomain> getDomainList(String prefix) { - log.log(LogLevel.DEBUG, String.format("getDomainList(prefix=%s)", prefix)); - return getOrThrow( - () -> { - DomainList domainList = zmsClient.getDomainList( - /*limit*/null, /*skip*/null, prefix, /*depth*/null, /*domain*/null, - /*productId*/ null, /*modifiedSince*/null); - return toAthensDomains(domainList.getNames()); - }); - } - - @Override - public AthenzPublicKey getPublicKey(AthenzService service, String keyId) { - log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getServiceName(), keyId); - return getOrThrow(() -> { - PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getServiceName(), keyId); - return fromYbase64EncodedKey(entry.getKey(), keyId); - }); - } - - @Override - public List<AthenzPublicKey> getPublicKeys(AthenzService service) { - log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getServiceName()); - return getOrThrow(() -> { - ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getServiceName()); - return toAthensPublicKeys(serviceIdentity.getPublicKeys()); - }); - } - - private static AthenzPublicKey fromYbase64EncodedKey(String encodedKey, String keyId) { - return new AthenzPublicKey(Crypto.loadPublicKey(Crypto.ybase64DecodeString(encodedKey)), keyId); - } - - private static List<TenantRoleAction> createTenantRoleActions() { - return Arrays.stream(ApplicationAction.values()) - .map(action -> new TenantRoleAction().setAction(action.name()).setRole(action.roleName)) - .collect(toList()); - } - - private static List<AthensDomain> toAthensDomains(List<String> domains) { - return domains.stream().map(AthensDomain::new).collect(toList()); - } - - private static List<AthenzPublicKey> toAthensPublicKeys(List<PublicKeyEntry> publicKeys) { - return publicKeys.stream() - .map(entry -> fromYbase64EncodedKey(entry.getKey(), entry.getId())) - .collect(toList()); - } - - private boolean hasAccess(String action, String resource, AthenzPrincipal principal) { - log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, principal); - return getOrThrow( - () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, principal.toYRN()).getGranted()); - } - - private static void log(String format, Object... args) { - log.log(LogLevel.DEBUG, String.format(format, args)); - } - - private static void runOrThrow(Runnable wrappedCode) { - try { - wrappedCode.run(); - } catch (ZMSClientException e) { - logWarning(e); - throw new ZmsException(e); - } - } - - private static <T> T getOrThrow(Supplier<T> wrappedCode) { - try { - return wrappedCode.get(); - } catch (ZMSClientException e) { - logWarning(e); - throw new ZmsException(e); - } - } - - private static void logWarning(ZMSClientException e) { - log.warning("Error from Athens: " + e.getMessage()); - } - - private String resourceStringPrefix(AthensDomain tenantDomain) { - return String.format("%s:service.%s.tenant.%s", - service.getDomain().getName(), service.getServiceName(), tenantDomain.id()); - } - - private String tenantResourceString(AthensDomain tenantDomain) { - return resourceStringPrefix(tenantDomain) + ".wildcard"; - } - - private String applicationResourceString(AthensDomain tenantDomain, ApplicationId applicationName) { - return resourceStringPrefix(tenantDomain) + "." + "res_group" + "." + applicationName.id() + ".wildcard"; - } - - private enum TenantAction { - // This is meant to match only the '*' action of the 'admin' role. - // If needed, we can replace it with 'create', 'delete' etc. later. - _modify_ - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java deleted file mode 100644 index 67f22d42f92..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.impl; - -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; - -import java.security.PublicKey; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - -/** - * Downloads and caches public keys for Athens services. - * - * @author bjorncs - */ -public class ZmsKeystoreImpl implements ZmsKeystore { - private static final Logger log = Logger.getLogger(ZmsKeystoreImpl.class.getName()); - - private final Map<FullKeyId, PublicKey> cachedKeys = new ConcurrentHashMap<>(); - private final AthenzClientFactory athenzClientFactory; - - ZmsKeystoreImpl(AthenzClientFactory factory) { - this.athenzClientFactory = factory; - } - - @Override - public Optional<PublicKey> getPublicKey(AthenzService service, String keyId) { - FullKeyId fullKeyId = new FullKeyId(service, keyId); - PublicKey cachedKey = cachedKeys.get(fullKeyId); - if (cachedKey != null) { - return Optional.of(cachedKey); - } - Optional<PublicKey> downloadedKey = downloadPublicKey(fullKeyId); - downloadedKey.ifPresent(key -> { - log.log(LogLevel.INFO, "Adding key " + fullKeyId + " to the cache"); - cachedKeys.put(fullKeyId, key); - }); - return downloadedKey; - } - - @Override - public void preloadKeys(AthenzService service) { - try { - log.log(LogLevel.INFO, "Downloading keys for " + service); - List<AthenzPublicKey> publicKeys = athenzClientFactory.createZmsClientWithServicePrincipal() - .getPublicKeys(service); - for (AthenzPublicKey publicKey : publicKeys) { - FullKeyId fullKeyId = new FullKeyId(service, publicKey.getKeyId()); - log.log(LogLevel.DEBUG, "Adding key " + fullKeyId + " to the cache"); - cachedKeys.put(fullKeyId, publicKey.getPublicKey()); - } - log.log(LogLevel.INFO, "Successfully downloaded keys for " + service); - } catch (ZmsException e) { - log.log(LogLevel.WARNING, "Failed to download keys for " + service + ": " + e.getMessage()); - } - } - - private Optional<PublicKey> downloadPublicKey(FullKeyId fullKeyId) { - try { - log.log(LogLevel.INFO, "Downloading key " + fullKeyId); - AthenzPublicKey publicKey = athenzClientFactory.createZmsClientWithServicePrincipal() - .getPublicKey(fullKeyId.service, fullKeyId.keyId); - return Optional.of(publicKey.getPublicKey()); - } catch (ZmsException e) { - if (e.getCode() == 404) { // Key does not exist - log.log(LogLevel.INFO, "Key " + fullKeyId + " not found"); - return Optional.empty(); - } - String msg = String.format("Unable to retrieve public key from Athens (%s): %s", fullKeyId, e.getMessage()); - throw createException(msg, e); - } - } - - private static RuntimeException createException(String message, Exception cause) { - log.log(LogLevel.ERROR, message); - return new RuntimeException(message, cause); - } - - private static class FullKeyId { - private final AthenzService service; - private final String keyId; - - private FullKeyId(AthenzService service, String keyId) { - this.service = service; - this.keyId = keyId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FullKeyId fullKeyId1 = (FullKeyId) o; - return Objects.equals(service, fullKeyId1.service) && - Objects.equals(keyId, fullKeyId1.keyId); - } - - @Override - public int hashCode() { - return Objects.hash(service, keyId); - } - - @Override - public String toString() { - return "FullKeyId{" + - "service=" + service + - ", keyId='" + keyId + '\'' + - '}'; - } - } -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java deleted file mode 100644 index d964cc967e8..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.impl; - -import com.yahoo.athenz.zts.TenantDomains; -import com.yahoo.athenz.zts.ZTSClient; -import com.yahoo.athenz.zts.ZTSClientException; -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZtsException; -import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; - -import java.util.List; -import java.util.logging.Logger; - -import static java.util.stream.Collectors.toList; - -/** - * @author bjorncs - */ -public class ZtsClientImpl implements ZtsClient { - - private static final Logger log = Logger.getLogger(ZtsClientImpl.class.getName()); - - private final ZTSClient ztsClient; - private final AthenzService service; - - public ZtsClientImpl(ZTSClient ztsClient, AthenzConfig config) { - this.ztsClient = ztsClient; - this.service = new AthenzService(config.domain(), config.service().name()); - } - - @Override - public List<AthensDomain> getTenantDomainsForUser(AthenzPrincipal principal) { - log.log(LogLevel.DEBUG, String.format( - "getTenantDomains(domain=%s, username=%s, rolename=admin, service=%s)", - service.getDomain().getName(), principal, service.getServiceName())); - try { - TenantDomains domains = ztsClient.getTenantDomains( - service.getDomain().getName(), principal.toYRN(), "admin", service.getServiceName()); - return domains.getTenantDomainNames().stream() - .map(AthensDomain::new) - .collect(toList()); - } catch (ZTSClientException e) { - throw new ZtsException(e); - } - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java deleted file mode 100644 index 92c7ba5a007..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.mock; - -import com.yahoo.component.AbstractComponent; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @author bjorncs - */ -public class AthenzClientFactoryMock extends AbstractComponent implements AthenzClientFactory { - - private static final Logger log = Logger.getLogger(AthenzClientFactoryMock.class.getName()); - - private final AthensDbMock athens; - - public AthenzClientFactoryMock() { - this(new AthensDbMock()); - } - - public AthenzClientFactoryMock(AthensDbMock athens) { - this.athens = athens; - } - - public AthensDbMock getSetup() { - return athens; - } - - @Override - public ZmsClient createZmsClientWithServicePrincipal() { - log("createZmsClientWithServicePrincipal()"); - return new ZmsClientMock(athens); - } - - @Override - public ZtsClient createZtsClientWithServicePrincipal() { - log("createZtsClientWithServicePrincipal()"); - return new ZtsClientMock(athens); - } - - @Override - public ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken) { - log("createZmsClientWithAuthorizedServiceToken(authorizedServiceToken='%s')", authorizedServiceToken); - return new ZmsClientMock(athens); - } - - private static void log(String format, Object... args) { - log.log(Level.INFO, String.format(format, args)); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java deleted file mode 100644 index fa41dcc6446..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.mock; - -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; - -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static java.util.stream.Collectors.toList; - -/** - * @author bjorncs - */ -public class ZtsClientMock implements ZtsClient { - private static final Logger log = Logger.getLogger(ZtsClientMock.class.getName()); - - private final AthensDbMock athens; - - public ZtsClientMock(AthensDbMock athens) { - this.athens = athens; - } - - @Override - public List<AthensDomain> getTenantDomainsForUser(AthenzPrincipal principal) { - log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", principal); - return athens.domains.values().stream() - .filter(domain -> domain.tenantAdmins.contains(principal) || domain.admins.contains(principal)) - .map(domain -> domain.name) - .collect(toList()); - } -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index cb6c47ad299..25625e82f1a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.restapi.application; import com.google.common.base.Joiner; -import com.google.inject.Inject; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; @@ -51,6 +50,9 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log; import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; @@ -65,10 +67,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; import com.yahoo.vespa.hosted.controller.restapi.MessageResponse; import com.yahoo.vespa.hosted.controller.restapi.Path; @@ -108,15 +106,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private final Controller controller; private final Authorizer authorizer; - private final AthenzClientFactory athenzClientFactory; - @Inject - public ApplicationApiHandler(Executor executor, AccessLog accessLog, Controller controller, Authorizer authorizer, - AthenzClientFactory athenzClientFactory) { + public ApplicationApiHandler(Executor executor, AccessLog accessLog, Controller controller, Authorizer authorizer) { super(executor, accessLog); this.controller = controller; this.authorizer = authorizer; - this.athenzClientFactory = athenzClientFactory; } @Override @@ -760,10 +754,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Inspector deployOptions = SlimeUtils.jsonToSlime(dataParts.get("deployOptions")).get(); - DeployAuthorizer deployAuthorizer = new DeployAuthorizer(controller.zoneRegistry(), athenzClientFactory); + DeployAuthorizer deployAuthorizer = new DeployAuthorizer(controller.athens(), controller.zoneRegistry()); Tenant tenant = controller.tenants().tenant(new TenantId(tenantName)).orElseThrow(() -> new NotExistsException(new TenantId(tenantName))); Principal principal = authorizer.getPrincipal(request); - if (principal instanceof AthenzPrincipal) { + if (principal instanceof AthensPrincipal) { deployAuthorizer.throwIfUnauthorizedForDeploy(principal, Environment.from(environment), tenant, diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java index 84e731ec994..8dff39779b9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java @@ -11,10 +11,10 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; import com.yahoo.vespa.hosted.controller.common.ContextAttributes; import com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter; import com.yahoo.vespa.hosted.controller.restapi.filter.UnauthenticatedUserPrincipal; @@ -47,13 +47,15 @@ public class Authorizer { new UserId("screwdriver-test")); private final Controller controller; - private final AthenzClientFactory athenzClientFactory; + private final ZmsClientFactory zmsClientFactory; private final EntityService entityService; + private final Athens athens; - public Authorizer(Controller controller, EntityService entityService, AthenzClientFactory athenzClientFactory) { + public Authorizer(Controller controller, EntityService entityService) { this.controller = controller; - this.athenzClientFactory = athenzClientFactory; + this.zmsClientFactory = controller.athens().zmsClientFactory(); this.entityService = entityService; + this.athens = controller.athens(); } public void throwIfUnauthorized(TenantId tenantId, HttpRequest request) throws ForbiddenException { @@ -88,19 +90,19 @@ public class Authorizer { public Optional<NToken> getNToken(HttpRequest request) { String nTokenHeader = (String)request.getJDiscRequest().context().get(NTokenRequestFilter.NTOKEN_HEADER); - return Optional.ofNullable(nTokenHeader).map(NToken::new); + return Optional.ofNullable(nTokenHeader).map(athens::nTokenFrom); } public boolean isSuperUser(HttpRequest request) { // TODO Check membership of admin role in Vespa's Athens domain - return isMemberOfVespaBouncerGroup(request) || isScrewdriverPrincipal(getPrincipal(request)); + return isMemberOfVespaBouncerGroup(request) || isScrewdriverPrincipal(athens, getPrincipal(request)); } - public static boolean isScrewdriverPrincipal(Principal principal) { + public static boolean isScrewdriverPrincipal(Athens athens, Principal principal) { if (principal instanceof UnauthenticatedUserPrincipal) // Host-based authentication return SCREWDRIVER_USERS.contains(new UserId(principal.getName())); - else if (principal instanceof AthenzPrincipal) - return ((AthenzPrincipal)principal).getDomain().equals(AthenzUtils.SCREWDRIVER_DOMAIN); + else if (principal instanceof AthensPrincipal) + return ((AthensPrincipal)principal).getDomain().equals(athens.screwdriverDomain()); else return false; } @@ -124,13 +126,13 @@ public class Authorizer { } private boolean isAthensTenantAdmin(UserId userId, AthensDomain tenantDomain) { - return athenzClientFactory.createZmsClientWithServicePrincipal() - .hasTenantAdminAccess(AthenzUtils.createPrincipal(userId), tenantDomain); + return zmsClientFactory.createClientWithServicePrincipal() + .hasTenantAdminAccess(athens.principalFrom(userId), tenantDomain); } public boolean isAthensDomainAdmin(UserId userId, AthensDomain tenantDomain) { - return athenzClientFactory.createZmsClientWithServicePrincipal() - .isDomainAdmin(AthenzUtils.createPrincipal(userId), tenantDomain); + return zmsClientFactory.createClientWithServicePrincipal() + .isDomainAdmin(athens.principalFrom(userId), tenantDomain); } public boolean isGroupMember(UserId userId, UserGroup userGroup) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java index fa82c9239df..5c7cdfdae0a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java @@ -7,12 +7,11 @@ import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.restapi.filter.UnauthenticatedUserPrincipal; import javax.ws.rs.ForbiddenException; @@ -31,12 +30,12 @@ public class DeployAuthorizer { private static final Logger log = Logger.getLogger(DeployAuthorizer.class.getName()); + private final Athens athens; private final ZoneRegistry zoneRegistry; - private final AthenzClientFactory athenzClientFactory; - public DeployAuthorizer(ZoneRegistry zoneRegistry, AthenzClientFactory athenzClientFactory) { + public DeployAuthorizer(Athens athens, ZoneRegistry zoneRegistry) { + this.athens = athens; this.zoneRegistry = zoneRegistry; - this.athenzClientFactory = athenzClientFactory; } public void throwIfUnauthorizedForDeploy(Principal principal, @@ -51,7 +50,7 @@ public class DeployAuthorizer { private boolean athensCredentialsRequired(Environment environment, Tenant tenant, ApplicationId applicationId, Principal principal) { if (!environmentRequiresAuthorization(environment)) return false; - if (! isScrewdriverPrincipal(principal)) + if (! isScrewdriverPrincipal(athens, principal)) throw loggedForbiddenException( "Principal '%s' is not a screwdriver principal, and does not have deploy access to application '%s'", principal.getName(), applicationId.toShortString()); @@ -63,10 +62,10 @@ public class DeployAuthorizer { // TODO: inline when deployment via ssh is removed private void checkAthensCredentials(Principal principal, Tenant tenant, ApplicationId applicationId) { AthensDomain domain = tenant.getAthensDomain().get(); - if (! (principal instanceof AthenzPrincipal)) + if (! (principal instanceof AthensPrincipal)) throw loggedForbiddenException("Principal '%s' is not authenticated.", principal.getName()); - AthenzPrincipal athensPrincipal = (AthenzPrincipal)principal; + AthensPrincipal athensPrincipal = (AthensPrincipal)principal; if ( ! hasDeployAccessToAthensApplication(athensPrincipal, domain, applicationId)) throw loggedForbiddenException( "Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " + @@ -96,14 +95,14 @@ public class DeployAuthorizer { if (athensCredentialsRequired(environment, tenant, applicationId, principal)) { ScrewdriverId screwdriverId = optionalScrewdriverId.orElseThrow( () -> loggedForbiddenException("Screwdriver id must be provided when deploying from Screwdriver.")); - principal = AthenzUtils.createPrincipal(screwdriverId); + principal = athens.principalFrom(screwdriverId); checkAthensCredentials(principal, tenant, applicationId); } } - private boolean hasDeployAccessToAthensApplication(AthenzPrincipal principal, AthensDomain domain, ApplicationId applicationId) { + private boolean hasDeployAccessToAthensApplication(AthensPrincipal principal, AthensDomain domain, ApplicationId applicationId) { try { - return athenzClientFactory.createZmsClientWithServicePrincipal() + return athens.zmsClientFactory().createClientWithServicePrincipal() .hasApplicationAccess( principal, ApplicationAction.deploy, diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/NTokenRequestFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/NTokenRequestFilter.java index 443a53b476d..0138d3ae65c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/NTokenRequestFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/NTokenRequestFilter.java @@ -5,7 +5,7 @@ import com.google.inject.Inject; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; import com.yahoo.yolean.chain.After; /** @@ -16,16 +16,16 @@ public class NTokenRequestFilter implements SecurityRequestFilter { public static final String NTOKEN_HEADER = "com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter.ntoken"; - private final String principalHeaderName; + private final Athens athens; @Inject - public NTokenRequestFilter(AthenzConfig config) { - this.principalHeaderName = config.principalHeaderName(); + public NTokenRequestFilter(Athens athens) { + this.athens = athens; } @Override public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { - String nToken = request.getHeader(principalHeaderName); + String nToken = request.getHeader(athens.principalTokenHeader()); if (nToken != null) { request.setAttribute(NTOKEN_HEADER, nToken); } diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def deleted file mode 100644 index 4e27e3ebd07..00000000000 --- a/controller-server/src/main/resources/configdefinitions/athenz.def +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -namespace=vespa.hosted.controller.athenz.config - -# Principal header name -principalHeaderName string default="Athenz-Principal-Auth" - -# URL to ZMS API endpoint -zmsUrl string - -# URL to ZTS API endpoint -ztsUrl string - -# Athenz domain for controller identity. The domain is also used for Athenz tenancy integration. -domain string - -# Athenz service name for controller identity -service.name string - -# Athenz service public key id -service.publicKeyId string - -# Version of Athenz service private key -service.privateKeyVersion int - -# Name of Athenz service private key secret -service.privateKeySecretName string diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index 408be6a49c2..b193fd7ef41 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -24,6 +24,9 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensDbMock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.NTokenMock; import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.ApplicationRevision; @@ -33,8 +36,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType; import com.yahoo.vespa.hosted.controller.application.JobStatus; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthensDbMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.BuildSystem; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; @@ -380,7 +381,7 @@ public class ControllerTest { assertFalse(mockDomain.isVespaTenant); // Migrate tenant to Athens - NToken nToken = TestIdentities.userNToken; + NToken nToken = new NTokenMock("token"); tester.controller().tenants().migrateTenantToAthens( tenantId, athensDomain, new PropertyId("1567"), new Property("vespa_dev.no"), nToken); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java index 8e1234b7e96..5184cde79c2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java @@ -21,6 +21,8 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensDbMock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock; import com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock; import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService; @@ -28,8 +30,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock; import com.yahoo.vespa.hosted.controller.api.integration.jira.JiraMock; import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthensDbMock; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.integration.MockMetricsService; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; @@ -214,7 +214,7 @@ public final class ControllerTester { new MockRoutingGenerator(), new ChefMock(), clock, - new AthenzClientFactoryMock(athensDb)); + new AthensMock(athensDb)); controller.updateVersionStatus(VersionStatus.compute(controller)); return controller; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java index 355b63335c0..1f52ebcadb7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.NTokenMock; import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.identifiers.EnvironmentId; import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId; @@ -9,9 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.filter.AthenzTestUtils; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; /** * @author Tony Vaagenes @@ -34,8 +33,6 @@ public class TestIdentities { public static Tenant tenant = Tenant.createOpsDbTenant(tenantId, userGroup1, property); - public static NToken userNToken = new NToken.Builder( - "U1", AthenzUtils.createPrincipal(userId), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") - .build(); + public static NToken userNToken = new NTokenMock("token"); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java deleted file mode 100644 index 20db038485d..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.filter; - -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ReadableContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.filter.DiscFilterRequest; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import org.junit.Before; -import org.junit.Test; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.util.Objects; - -import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED; -import static java.util.stream.Collectors.joining; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author bjorncs - */ -public class AthenzPrincipalFilterTest { - - private static final NToken NTOKEN = createDummyToken(); - private static final String ATHENZ_PRINCIPAL_HEADER = "Athenz-Principal-Auth"; - - private NTokenValidator validator; - private AthenzPrincipal principal; - - @Before - public void before() { - validator = mock(NTokenValidator.class); - principal = AthenzUtils.createPrincipal(new UserId("bob")); - } - - @Test - public void valid_ntoken_is_accepted() throws Exception { - DiscFilterRequest request = mock(DiscFilterRequest.class); - when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getToken()); - - when(validator.validate(NTOKEN)).thenReturn(principal); - - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); - filter.filter(request, new ResponseHandlerMock()); - - verify(request).setUserPrincipal(principal); - } - - @Test - public void missing_token_is_unauthorized() throws Exception { - DiscFilterRequest request = mock(DiscFilterRequest.class); - when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(null); - - ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); - filter.filter(request, responseHandler); - - assertThat(responseHandler.response, notNullValue()); - assertThat(responseHandler.response.getStatus(), equalTo(UNAUTHORIZED)); - assertThat(responseHandler.getResponseContent(), containsString("NToken is missing")); - } - - @Test - public void invalid_token_is_unauthorized() throws Exception { - DiscFilterRequest request = mock(DiscFilterRequest.class); - when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getToken()); - - when(validator.validate(NTOKEN)).thenThrow(new InvalidTokenException("Invalid token")); - - ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); - filter.filter(request, responseHandler); - - assertThat(responseHandler.response, notNullValue()); - assertThat(responseHandler.response.getStatus(), equalTo(UNAUTHORIZED)); - assertThat(responseHandler.getResponseContent(), containsString("Invalid token")); - } - - private static NToken createDummyToken() { - return new NToken.Builder( - "U1", AthenzUtils.createPrincipal(new UserId("bob")), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") - .build(); - } - - private static class ResponseHandlerMock implements ResponseHandler { - - public Response response; - public ReadableContentChannel contentChannel; - - @Override - public ContentChannel handleResponse(Response r) { - response = Objects.requireNonNull(r); - contentChannel = new ReadableContentChannel(); - return contentChannel; - } - - public String getResponseContent() { - try (BufferedReader br = new BufferedReader(new InputStreamReader(contentChannel.toStream()))) { - return br.lines().collect(joining(System.lineSeparator())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTestUtils.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTestUtils.java deleted file mode 100644 index 40b38254dda..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTestUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.filter; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; - -/** - * @author bjorncs - */ -public class AthenzTestUtils { - public static KeyPair generateRsaKeypair() { - try { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - keyGen.initialize(512); - return keyGen.genKeyPair(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java deleted file mode 100644 index e269f2842e2..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz.filter; - -import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.util.Optional; - -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; -import static org.junit.Assert.assertEquals; - -/** - * @author bjorncs - */ -public class NTokenValidatorTest { - - private static final KeyPair TRUSTED_KEY = AthenzTestUtils.generateRsaKeypair(); - private static final KeyPair UNKNOWN_KEY = AthenzTestUtils.generateRsaKeypair(); - private static final AthenzPrincipal PRINCIPAL = new AthenzPrincipal(new AthensDomain("yby"), new UserId("user")); - - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void valid_token_is_accepted() throws NoSuchAlgorithmException, InvalidTokenException { - NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0"); - AthenzPrincipal principal = validator.validate(token); - assertEquals("yby.user", principal.toYRN()); - } - - @Test - public void invalid_signature_is_not_accepted() throws InvalidTokenException { - NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), UNKNOWN_KEY, "0"); - exceptionRule.expect(InvalidTokenException.class); - exceptionRule.expectMessage("NToken is expired or has invalid signature"); - validator.validate(token); - } - - @Test - public void expired_token_is_not_accepted() throws InvalidTokenException { - NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, 1234 /*long time ago*/, TRUSTED_KEY, "0"); - exceptionRule.expect(InvalidTokenException.class); - exceptionRule.expectMessage("NToken is expired or has invalid signature"); - validator.validate(token); - } - - @Test - public void unknown_keyId_is_not_accepted() throws InvalidTokenException { - NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "unknown-key-id"); - exceptionRule.expect(InvalidTokenException.class); - exceptionRule.expectMessage("NToken has an unknown keyId"); - validator.validate(token); - } - - @Test - public void failing_to_find_key_should_throw_exception() throws InvalidTokenException { - ZmsKeystore keystore = (athensService, keyId) -> { throw new RuntimeException(); }; - NTokenValidator validator = new NTokenValidator(keystore); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0"); - exceptionRule.expect(InvalidTokenException.class); - exceptionRule.expectMessage("Failed to retrieve public key"); - validator.validate(token); - } - - private static ZmsKeystore createKeystore() { - return (athensService, keyId) -> - athensService.equals(ZMS_ATHENZ_SERVICE) && keyId.equals("0") - ? Optional.of(TRUSTED_KEY.getPublic()) - : Optional.empty(); - } - - private static NToken createNToken(AthenzPrincipal principal, long issueTime, KeyPair keyPair, String keyId) { - return new NToken.Builder("U1", principal, keyPair.getPrivate(), keyId) - .salt("1234") - .hostname("host") - .ip("1.2.3.4") - .issueTime(issueTime / 1000) - .expirationWindow(1000) - .build(); - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java index 99381d538d5..a792626d691 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java @@ -21,11 +21,13 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensDbMock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.ZmsClientFactoryMock; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthensDbMock; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.maintenance.JobControl; import com.yahoo.vespa.hosted.controller.maintenance.Upgrader; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; @@ -95,12 +97,14 @@ public class ContainerControllerTester { } public AthensDomain addTenantAthensDomain(String domainName, String userName) { - AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components() - .getComponent(AthenzClientFactoryMock.class.getName()); + Athens athens = (AthensMock) containerTester.container().components().getComponent( + "com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock" + ); + ZmsClientFactoryMock mock = (ZmsClientFactoryMock) athens.zmsClientFactory(); AthensDomain athensDomain = new AthensDomain(domainName); AthensDbMock.Domain domain = new AthensDbMock.Domain(athensDomain); domain.markAsVespaTenant(); - domain.admin(new AthenzPrincipal(new AthensDomain("domain"), new UserId(userName))); + domain.admin(new AthensPrincipal(new AthensDomain("domain"), new UserId(userName))); mock.getSetup().addDomain(domain); return athensDomain; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 5c102b0d9df..fd07428126a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -36,7 +36,7 @@ public class ControllerContainerTest { " <system>main</system>" + " </config>" + " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>" + - " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock'/>" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService'/>" + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 0c9ebedc09b..b6b0d4b2e51 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -10,6 +10,11 @@ import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ConfigServerClientMock; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensDbMock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock; +import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.ZmsClientFactoryMock; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.ClusterInfo; @@ -17,10 +22,6 @@ import com.yahoo.vespa.hosted.controller.application.ClusterUtilization; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthensDbMock; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; @@ -54,7 +55,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .region("corp-us-east-1") .build(); private static final String athensUserDomain = "domain1"; - private static final String athensScrewdriverDomain = AthenzUtils.SCREWDRIVER_DOMAIN.id(); + private static final String athensScrewdriverDomain = "screwdriver-domain"; @Test @@ -671,12 +672,14 @@ public class ApplicationApiTest extends ControllerContainerTest { * mock setup to replicate the action. */ private AthensDomain addTenantAthensDomain(String domainName, String userName) { - AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() - .getComponent(AthenzClientFactoryMock.class.getName()); + Athens athens = (AthensMock) container.components().getComponent( + "com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock" + ); + ZmsClientFactoryMock mock = (ZmsClientFactoryMock) athens.zmsClientFactory(); AthensDomain athensDomain = new AthensDomain(domainName); AthensDbMock.Domain domain = new AthensDbMock.Domain(athensDomain); domain.markAsVespaTenant(); - domain.admin(AthenzUtils.createPrincipal(new UserId(userName))); + domain.admin(new AthensPrincipal(new AthensDomain(athensUserDomain), new UserId(userName))); mock.getSetup().addDomain(domain); return athensDomain; } @@ -686,10 +689,12 @@ public class ApplicationApiTest extends ControllerContainerTest { * mock setup to replicate the action. */ private void addScrewdriverUserToDomain(String screwdriverUserId, String domainName) { - AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() - .getComponent(AthenzClientFactoryMock.class.getName()); + Athens athens = (AthensMock) container.components().getComponent( + "com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock" + ); + ZmsClientFactoryMock mock = (ZmsClientFactoryMock) athens.zmsClientFactory(); AthensDbMock.Domain domain = mock.getSetup().domains.get(new AthensDomain(domainName)); - domain.admin(new AthenzPrincipal(new AthensDomain(athensScrewdriverDomain), new UserId(screwdriverUserId))); + domain.admin(new AthensPrincipal(new AthensDomain(athensScrewdriverDomain), new UserId(screwdriverUserId))); } private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application, long projectId, diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java index 6f8dfc681ac..16557157cf5 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java @@ -6,10 +6,9 @@ import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TestIdentities; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.api.integration.athens.AthensPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.NToken; import javax.ws.rs.core.SecurityContext; import java.security.Principal; @@ -24,15 +23,15 @@ import java.util.Optional; @SuppressWarnings("unused") // injected public class MockAuthorizer extends Authorizer { - public MockAuthorizer(Controller controller, EntityService entityService, AthenzClientFactory athenzClientFactory) { - super(controller, entityService, athenzClientFactory); + public MockAuthorizer(Controller controller, EntityService entityService) { + super(controller, entityService); } /** Returns a principal given by the request parameters 'domain' and 'user' */ @Override public Optional<Principal> getPrincipalIfAny(HttpRequest request) { if (request.getProperty("user") == null) return Optional.empty(); - return Optional.of(new AthenzPrincipal(new AthensDomain(request.getProperty("domain")), + return Optional.of(new AthensPrincipal(new AthensDomain(request.getProperty("domain")), new UserId(request.getProperty("user")))); } @@ -37,18 +37,6 @@ <url>git@github.com:vespa-engine/vespa.git</url> </scm> - <repositories> - <!-- Required for Athenz libraries --> - <repository> - <snapshots> - <enabled>false</enabled> - </snapshots> - <id>bintray-yahoo-maven</id> - <name>bintray</name> - <url>http://yahoo.bintray.com/maven</url> - </repository> - </repositories> - <build> <finalName>${project.artifactId}</finalName> <extensions> @@ -910,16 +898,6 @@ <artifactId>icu4j</artifactId> <version>57.1</version> </dependency> - <dependency> - <groupId>com.yahoo.athenz</groupId> - <artifactId>athenz-zms-java-client</artifactId> - <version>${athenz.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.athenz</groupId> - <artifactId>athenz-zts-java-client</artifactId> - <version>${athenz.version}</version> - </dependency> </dependencies> </dependencyManagement> |