diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/SecretError.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/SecretError.java new file mode 100644 index 000000000..a9a5af4f1 --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/SecretError.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets; + +import com.netflix.spinnaker.kork.exceptions.HasAdditionalAttributes; + +/** Common interface for exceptions that have a corresponding {@link SecretErrorCode}. */ +public interface SecretError extends HasAdditionalAttributes { + /** Returns the error code constant for this error. */ + String getErrorCode(); + + /** Returns the exception message provided by this error. */ + String getMessage(); +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/SecretErrorCode.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/SecretErrorCode.java new file mode 100644 index 000000000..ee806088c --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/SecretErrorCode.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets; + +import lombok.Getter; + +/** Standard error codes and messages for various secret errors. */ +public enum SecretErrorCode implements SecretError { + /** @see com.netflix.spinnaker.kork.secrets.user.InvalidUserSecretReferenceException */ + INVALID_USER_SECRET_URI("user.format.invalid", "Invalid user secret URI format"), + /** @see com.netflix.spinnaker.kork.secrets.user.MissingUserSecretMetadataException */ + MISSING_USER_SECRET_METADATA("user.metadata.missing", "Missing user secret metadata"), + /** @see com.netflix.spinnaker.kork.secrets.user.InvalidUserSecretMetadataException */ + INVALID_USER_SECRET_METADATA("user.metadata.invalid", "Invalid user secret metadata"), + /** @see com.netflix.spinnaker.kork.secrets.user.UnsupportedUserSecretEngineException */ + UNSUPPORTED_USER_SECRET_ENGINE( + "user.engine.unsupported", "SecretEngine does not support user secrets"), + /** @see com.netflix.spinnaker.kork.secrets.user.UnsupportedUserSecretEncodingException */ + UNSUPPORTED_USER_SECRET_ENCODING( + "user.encoding.unsupported", "Unsupported user secret 'encoding'"), + /** @see com.netflix.spinnaker.kork.secrets.user.UnsupportedUserSecretTypeException */ + UNSUPPORTED_USER_SECRET_TYPE("user.type.unsupported", "Unsupported user secret 'type'"), + /** @see com.netflix.spinnaker.kork.secrets.user.InvalidUserSecretDataException */ + INVALID_USER_SECRET_DATA("user.data.invalid", "Invalid user secret data"), + /** @see SecretDecryptionException */ + USER_SECRET_DECRYPTION_FAILURE("user.decrypt.failure", "Unable to decrypt user secret"), + DENIED_ACCESS_TO_USER_SECRET("user.access.deny", "Denied access to user secret"), + MISSING_USER_SECRET_DATA_KEY("user.data.missing", "Missing user secret data for requested key"), + INVALID_EXTERNAL_SECRET_URI("external.format.invalid", "Invalid external secret URI format"), + /** @see SecretDecryptionException */ + EXTERNAL_SECRET_DECRYPTION_FAILURE( + "external.decrypt.failure", "Unable to decrypt external secret"), + DENIED_ACCESS_TO_EXTERNAL_SECRET("external.access.deny", "Denied access to external secret"), + /** @see UnsupportedSecretEngineException */ + UNSUPPORTED_SECRET_ENGINE("engine.unsupported", "Unsupported secret engine identifier"), + ; + + @Getter private final String errorCode; + @Getter private final String message; + + SecretErrorCode(String errorCode, String message) { + this.errorCode = "secrets." + errorCode; + this.message = message; + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/UnsupportedSecretEngineException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/UnsupportedSecretEngineException.java new file mode 100644 index 000000000..c2e31c58a --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/UnsupportedSecretEngineException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets; + +/** Exception thrown when an unsupported secret engine is called upon for decrypting a secret. */ +public class UnsupportedSecretEngineException extends SecretDecryptionException + implements SecretError { + public UnsupportedSecretEngineException(String engine) { + super(String.format("Unsupported secret engine identifier '%s'", engine)); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.UNSUPPORTED_SECRET_ENGINE.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/DefaultUserSecretSerde.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/DefaultUserSecretSerde.java index db5e82c0c..ae7f9b4ba 100644 --- a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/DefaultUserSecretSerde.java +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/DefaultUserSecretSerde.java @@ -20,12 +20,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.spinnaker.kork.annotations.NonnullByDefault; -import com.netflix.spinnaker.kork.secrets.SecretDecryptionException; -import com.netflix.spinnaker.kork.secrets.SecretException; import java.io.IOException; import java.util.Collection; import java.util.Map; -import java.util.Objects; import java.util.TreeMap; /** @@ -62,22 +59,33 @@ public boolean supports(UserSecretMetadata metadata) { @Override public UserSecret deserialize(byte[] encoded, UserSecretMetadata metadata) { - var type = Objects.requireNonNull(userSecretTypes.get(metadata.getType())); - var mapper = Objects.requireNonNull(mappersByEncodingFormat.get(metadata.getEncoding())); + var type = userSecretTypes.get(metadata.getType()); + if (type == null) { + throw new UnsupportedUserSecretTypeException(metadata.getType()); + } + var mapper = mappersByEncodingFormat.get(metadata.getEncoding()); + if (mapper == null) { + throw new UnsupportedUserSecretEncodingException(metadata.getEncoding()); + } + UserSecretData data; try { - return UserSecret.builder().metadata(metadata).data(mapper.readValue(encoded, type)).build(); + data = mapper.readValue(encoded, type); } catch (IOException e) { - throw new SecretDecryptionException(e); + throw new InvalidUserSecretDataException("cannot parse user secret data", e); } + return UserSecret.builder().metadata(metadata).data(data).build(); } @Override public byte[] serialize(UserSecretData secret, UserSecretMetadata metadata) { - var mapper = Objects.requireNonNull(mappersByEncodingFormat.get(metadata.getEncoding())); + var mapper = mappersByEncodingFormat.get(metadata.getEncoding()); + if (mapper == null) { + throw new UnsupportedUserSecretEncodingException(metadata.getEncoding()); + } try { return mapper.writeValueAsBytes(secret); } catch (JsonProcessingException e) { - throw new SecretException(e); + throw new InvalidUserSecretDataException(e.getMessage(), e); } } } diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretDataException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretDataException.java new file mode 100644 index 000000000..803f6c969 --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretDataException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.secrets.SecretDecryptionException; +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; + +/** + * Exception thrown when a {@link UserSecretSerde} is unable to deserialize data into {@link + * UserSecretData}. + */ +public class InvalidUserSecretDataException extends SecretDecryptionException + implements SecretError { + public InvalidUserSecretDataException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.INVALID_USER_SECRET_DATA.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretMetadataException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretMetadataException.java new file mode 100644 index 000000000..d32b9679f --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretMetadataException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.exceptions.HasAdditionalAttributes; +import com.netflix.spinnaker.kork.secrets.InvalidSecretFormatException; +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; +import lombok.Getter; + +/** + * Exception thrown when a decrypted {@link UserSecret} has invalid {@link UserSecretMetadata} + * defined. + */ +public class InvalidUserSecretMetadataException extends InvalidSecretFormatException + implements HasAdditionalAttributes, SecretError { + @Getter private final UserSecretReference userSecretReference; + + public InvalidUserSecretMetadataException( + UserSecretReference userSecretReference, Throwable cause) { + super(String.format("User secret %s has invalid metadata", userSecretReference), cause); + this.userSecretReference = userSecretReference; + } + + @Override + public String getErrorCode() { + return SecretErrorCode.INVALID_USER_SECRET_METADATA.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretReferenceException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretReferenceException.java new file mode 100644 index 000000000..c0053421a --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/InvalidUserSecretReferenceException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.exceptions.HasAdditionalAttributes; +import com.netflix.spinnaker.kork.secrets.InvalidSecretFormatException; +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; + +/** Exception thrown when an invalid {@link UserSecretReference} is attempted to be parsed. */ +@Getter +public class InvalidUserSecretReferenceException extends InvalidSecretFormatException + implements HasAdditionalAttributes, SecretError { + private final Map additionalAttributes = new HashMap<>(); + + public InvalidUserSecretReferenceException(String input, Throwable cause) { + super("Unable to parse input into a URI", cause); + additionalAttributes.put("input", input); + } + + public InvalidUserSecretReferenceException(String message, URI uri) { + super(message); + additionalAttributes.put("input", uri); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.INVALID_USER_SECRET_URI.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/MissingUserSecretMetadataException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/MissingUserSecretMetadataException.java new file mode 100644 index 000000000..5d19b5e0c --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/MissingUserSecretMetadataException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.exceptions.HasAdditionalAttributes; +import com.netflix.spinnaker.kork.secrets.InvalidSecretFormatException; +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; +import java.util.Map; +import lombok.Getter; + +/** + * Exception thrown when a decrypted {@link UserSecret} does not have any {@link UserSecretMetadata} + * defined. + */ +public class MissingUserSecretMetadataException extends InvalidSecretFormatException + implements HasAdditionalAttributes, SecretError { + @Getter private final UserSecretReference userSecretReference; + + public MissingUserSecretMetadataException(UserSecretReference userSecretReference) { + super(String.format("User secret %s has no metadata defined", userSecretReference)); + this.userSecretReference = userSecretReference; + } + + @Override + public Map getAdditionalAttributes() { + return Map.of("userSecretReference", userSecretReference); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.MISSING_USER_SECRET_METADATA.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretData.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretData.java index 0533b0644..d91d26ef1 100644 --- a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretData.java +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretData.java @@ -30,4 +30,9 @@ public class StringUserSecretData implements UserSecretData { public String getSecretString(String key) { return data; } + + @Override + public String getSecretString() { + return data; + } } diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretSerde.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretSerde.java index 0a87db5e3..0dce73bcc 100644 --- a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretSerde.java +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/StringUserSecretSerde.java @@ -36,6 +36,6 @@ public UserSecret deserialize(byte[] encoded, UserSecretMetadata metadata) { @Override public byte[] serialize(UserSecretData secret, UserSecretMetadata metadata) { - return secret.getSecretString("").getBytes(StandardCharsets.UTF_8); + return secret.getSecretString().getBytes(StandardCharsets.UTF_8); } } diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretEncodingException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretEncodingException.java new file mode 100644 index 000000000..7343f5f3e --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretEncodingException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.secrets.SecretDecryptionException; +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; + +/** + * Exception thrown when a {@link UserSecretSerde} encounters an unsupported {@linkplain + * UserSecretMetadata#getEncoding() encoding type}. + */ +public class UnsupportedUserSecretEncodingException extends SecretDecryptionException + implements SecretError { + public UnsupportedUserSecretEncodingException(String encoding) { + super(String.format("Unsupported user secret encoding '%s'", encoding)); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.UNSUPPORTED_USER_SECRET_ENCODING.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretEngineException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretEngineException.java new file mode 100644 index 000000000..cc7e4ecd2 --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretEngineException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; + +/** + * Exception thrown by a {@link com.netflix.spinnaker.kork.secrets.SecretEngine} when an attempt is + * made to get a {@link UserSecret} while said engine does not support user secrets. + */ +public class UnsupportedUserSecretEngineException extends UnsupportedOperationException + implements SecretError { + public UnsupportedUserSecretEngineException(String engine) { + super(String.format("Unsupported secret engine identifier '%s' for user secrets", engine)); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.UNSUPPORTED_USER_SECRET_ENGINE.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretTypeException.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretTypeException.java new file mode 100644 index 000000000..f45230d3e --- /dev/null +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UnsupportedUserSecretTypeException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Apple Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.kork.secrets.user; + +import com.netflix.spinnaker.kork.secrets.SecretDecryptionException; +import com.netflix.spinnaker.kork.secrets.SecretError; +import com.netflix.spinnaker.kork.secrets.SecretErrorCode; + +/** + * Exception thrown when a {@link UserSecretSerde} is unable to serialize or deserialize a secret. + */ +public class UnsupportedUserSecretTypeException extends SecretDecryptionException + implements SecretError { + public UnsupportedUserSecretTypeException(String type) { + super(String.format("Unsupported user secret type '%s'", type)); + } + + @Override + public String getErrorCode() { + return SecretErrorCode.UNSUPPORTED_USER_SECRET_TYPE.getErrorCode(); + } +} diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretData.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretData.java index 8048812f3..bd020c5b3 100644 --- a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretData.java +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretData.java @@ -17,9 +17,27 @@ package com.netflix.spinnaker.kork.secrets.user; import com.netflix.spinnaker.kork.annotations.NonnullByDefault; +import java.util.NoSuchElementException; @NonnullByDefault public interface UserSecretData { - /** Gets the value of this secret with the provided key and returns a string encoding of it. */ + /** + * Gets the value of this secret with the provided key and returns a string encoding of it. + * + * @param key the key to look up the secret value for in this data; can be an empty string for + * flat secrets + * @return the secret value encoded as a string + * @throws NoSuchElementException if no secret value exists for the given key + */ String getSecretString(String key); + + /** + * Gets the value of this secret as a single string if the underlying secret data supports it. + * + * @return the secret payload as a string + * @throws UnsupportedOperationException if this secret doesn't support scalar strings + */ + default String getSecretString() { + throw new UnsupportedOperationException(); + } } diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretManager.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretManager.java index d94c0113f..52ae77d4d 100644 --- a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretManager.java +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretManager.java @@ -18,9 +18,11 @@ import com.netflix.spinnaker.kork.annotations.NonnullByDefault; import com.netflix.spinnaker.kork.secrets.EncryptedSecret; +import com.netflix.spinnaker.kork.secrets.InvalidSecretFormatException; import com.netflix.spinnaker.kork.secrets.SecretDecryptionException; import com.netflix.spinnaker.kork.secrets.SecretEngine; import com.netflix.spinnaker.kork.secrets.SecretEngineRegistry; +import com.netflix.spinnaker.kork.secrets.UnsupportedSecretEngineException; import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -42,15 +44,27 @@ public class UserSecretManager { * * @param reference parsed user secret reference to fetch * @return the decrypted user secret + * @throws UnsupportedSecretEngineException if the secret reference does not have a corresponding + * secret engine + * @throws UnsupportedUserSecretEngineException if the secret engine does not support user secrets + * @throws MissingUserSecretMetadataException if the secret is missing its {@link + * UserSecretMetadata} + * @throws InvalidUserSecretMetadataException if the secret has metadata that cannot be parsed + * @throws InvalidSecretFormatException if the secret reference has other validation errors + * @throws SecretDecryptionException if the secret reference cannot be fetched */ public UserSecret getUserSecret(UserSecretReference reference) { String engineIdentifier = reference.getEngineIdentifier(); SecretEngine engine = registry.getEngine(engineIdentifier); if (engine == null) { - throw new SecretDecryptionException("Unknown secret engine identifier: " + engineIdentifier); + throw new UnsupportedSecretEngineException(engineIdentifier); } engine.validate(reference); - return engine.decrypt(reference); + try { + return engine.decrypt(reference); + } catch (UnsupportedOperationException e) { + throw new UnsupportedSecretEngineException(engineIdentifier); + } } /** @@ -59,12 +73,15 @@ public UserSecret getUserSecret(UserSecretReference reference) { * * @param reference parsed external secret reference to fetch * @return the decrypted external secret + * @throws SecretDecryptionException if the external secret does not have a corresponding secret + * engine or cannot be fetched + * @throws InvalidSecretFormatException if the external secret reference is invalid */ public byte[] getExternalSecret(EncryptedSecret reference) { String engineIdentifier = reference.getEngineIdentifier(); SecretEngine engine = registry.getEngine(engineIdentifier); if (engine == null) { - throw new SecretDecryptionException("Unknown secret engine identifier: " + engineIdentifier); + throw new UnsupportedSecretEngineException(engineIdentifier); } engine.validate(reference); return engine.decrypt(reference); @@ -76,6 +93,9 @@ public byte[] getExternalSecret(EncryptedSecret reference) { * * @param reference parsed external secret reference to fetch * @return the decrypted external secret string + * @throws SecretDecryptionException if the external secret does not have a corresponding secret + * engine or cannot be fetched + * @throws InvalidSecretFormatException if the external secret reference is invalid */ public String getExternalSecretString(EncryptedSecret reference) { return new String(getExternalSecret(reference), StandardCharsets.UTF_8); diff --git a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretSerde.java b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretSerde.java index 9c291a0a6..186a02491 100644 --- a/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretSerde.java +++ b/kork-secrets/src/main/java/com/netflix/spinnaker/kork/secrets/user/UserSecretSerde.java @@ -17,12 +17,38 @@ package com.netflix.spinnaker.kork.secrets.user; import com.netflix.spinnaker.kork.annotations.NonnullByDefault; +import com.netflix.spinnaker.kork.secrets.SecretDecryptionException; +import com.netflix.spinnaker.kork.secrets.SecretException; @NonnullByDefault public interface UserSecretSerde { + + /** + * Checks if this serde supports user secrets with the given metadata. + * + * @param metadata the user secret metadata to check for support + * @return true if this serde can serialize and deserialize user secrets with the given metadata + */ boolean supports(UserSecretMetadata metadata); + /** + * Deserializes a raw user secret payload with its parsed metadata. + * + * @param encoded the raw user secret data + * @param metadata the parsed user secret metadata corresponding to the given raw secret + * @return the parsed user secret + * @throws SecretDecryptionException if the user secret data cannot be parsed as configured by the + * metadata + */ UserSecret deserialize(byte[] encoded, UserSecretMetadata metadata); + /** + * Serializes a raw user secret to the specified encoding in the given metadata. + * + * @param secret the user secret data + * @param metadata the metadata describing the user secret + * @return the serialized user secret + * @throws SecretException if the user secret cannot be serialized + */ byte[] serialize(UserSecretData secret, UserSecretMetadata metadata); }