Module nitric.api.secrets

Expand source code
#
# Copyright (c) 2021 Nitric Technologies Pty Ltd.
#
# This file is part of Nitric Python 3 SDK.
# See https://github.com/nitrictech/python-sdk for further info.
#
# 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.
#
from __future__ import annotations
from dataclasses import dataclass
from typing import Union

from grpclib import GRPCError

from nitric.exception import exception_from_grpc_error
from nitric.utils import new_default_channel
from nitric.proto.nitric.secret.v1 import (
    SecretServiceStub,
    Secret as SecretMessage,
    SecretVersion as VersionMessage,
    SecretPutRequest,
    SecretAccessRequest,
)


class Secrets(object):
    """
    Nitric secrets management client.

    This client insulates application code from stack specific secrets managements services.
    """

    def __init__(self):
        """Construct a Nitric Storage Client."""
        self._channel = new_default_channel()
        self._secrets_stub = SecretServiceStub(channel=self._channel)

    def __del__(self):
        # close the channel when this client is destroyed
        if self._channel is not None:
            self._channel.close()

    def secret(self, name: str):
        """Return a reference to a secret container from the connected secrets management service."""
        return SecretContainerRef(_secrets=self, name=name)


def _secret_to_wire(secret: SecretContainerRef) -> SecretMessage:
    return SecretMessage(name=secret.name)


@dataclass(frozen=True)
class SecretContainerRef(object):
    """A reference to a secret container, used to store and retrieve secret versions."""

    _secrets: Secrets
    name: str

    async def put(self, value: Union[str, bytes]) -> SecretVersion:
        """
        Create a new secret version, making it the latest and storing the provided value.

        :param value: the secret value to store
        """
        if isinstance(value, str):
            value = bytes(value, "utf-8")

        secret_message = _secret_to_wire(self)

        try:
            response = await self._secrets._secrets_stub.put(
                secret_put_request=SecretPutRequest(secret=secret_message, value=value)
            )
            return self.version(version=response.secret_version.version)
        except GRPCError as grpc_err:
            raise exception_from_grpc_error(grpc_err)

    def version(self, version: str):
        """
        Return a reference to a specific version of a secret.

        Can be used to retrieve the secret value associated with the version.
        """
        return SecretVersion(_secrets=self._secrets, secret=self, id=version)

    def latest(self):
        """
        Return a reference to the 'latest' secret version.

        Note: using 'access' on this reference may return different values between requests if a
        new version is created between access calls.
        """
        return self.version("latest")


def _secret_version_to_wire(version: SecretVersion) -> VersionMessage:
    return VersionMessage(_secret_to_wire(version.secret), version=version.id)


@dataclass(frozen=True)
class SecretVersion(object):
    """A reference to a version of a secret, used to access the value of the version."""

    _secrets: Secrets
    secret: SecretContainerRef
    id: str

    async def access(self) -> SecretValue:
        """Return the value stored against this version of the secret."""
        version_message = _secret_version_to_wire(self)
        try:
            response = await self._secrets._secrets_stub.access(
                secret_access_request=SecretAccessRequest(secret_version=version_message)
            )
        except GRPCError as grpc_err:
            raise exception_from_grpc_error(grpc_err)

        # Construct a new SecretVersion if the response version id doesn't match this reference.
        # This ensures calls to access from the 'latest' version return new version objects
        # with a fixed version id.
        static_version = (
            self
            if response.secret_version.version == self.id
            else SecretVersion(_secrets=self._secrets, secret=self.secret, id=response.secret_version.version)
        )

        return SecretValue(version=static_version, value=response.value)


@dataclass(frozen=True)
class SecretValue(object):
    """Represents the value of a secret, tied to a specific version."""

    # The version containing this value. Never 'latest', always a specific version.
    version: SecretVersion
    value: bytes

    def __str__(self) -> str:
        return self.value.decode("utf-8")

    def __bytes__(self) -> bytes:
        return self.value

    def as_string(self):
        """Return the content of this secret value as a string."""
        return str(self)

    def as_bytes(self):
        """Return the content of this secret value."""
        return bytes(self)

Classes

class SecretContainerRef (_secrets: Secrets, name: str)

A reference to a secret container, used to store and retrieve secret versions.

Expand source code
@dataclass(frozen=True)
class SecretContainerRef(object):
    """A reference to a secret container, used to store and retrieve secret versions."""

    _secrets: Secrets
    name: str

    async def put(self, value: Union[str, bytes]) -> SecretVersion:
        """
        Create a new secret version, making it the latest and storing the provided value.

        :param value: the secret value to store
        """
        if isinstance(value, str):
            value = bytes(value, "utf-8")

        secret_message = _secret_to_wire(self)

        try:
            response = await self._secrets._secrets_stub.put(
                secret_put_request=SecretPutRequest(secret=secret_message, value=value)
            )
            return self.version(version=response.secret_version.version)
        except GRPCError as grpc_err:
            raise exception_from_grpc_error(grpc_err)

    def version(self, version: str):
        """
        Return a reference to a specific version of a secret.

        Can be used to retrieve the secret value associated with the version.
        """
        return SecretVersion(_secrets=self._secrets, secret=self, id=version)

    def latest(self):
        """
        Return a reference to the 'latest' secret version.

        Note: using 'access' on this reference may return different values between requests if a
        new version is created between access calls.
        """
        return self.version("latest")

Class variables

var name : str

Methods

def latest(self)

Return a reference to the 'latest' secret version.

Note: using 'access' on this reference may return different values between requests if a new version is created between access calls.

Expand source code
def latest(self):
    """
    Return a reference to the 'latest' secret version.

    Note: using 'access' on this reference may return different values between requests if a
    new version is created between access calls.
    """
    return self.version("latest")
async def put(self, value: Union[str, bytes]) ‑> SecretVersion

Create a new secret version, making it the latest and storing the provided value.

:param value: the secret value to store

Expand source code
async def put(self, value: Union[str, bytes]) -> SecretVersion:
    """
    Create a new secret version, making it the latest and storing the provided value.

    :param value: the secret value to store
    """
    if isinstance(value, str):
        value = bytes(value, "utf-8")

    secret_message = _secret_to_wire(self)

    try:
        response = await self._secrets._secrets_stub.put(
            secret_put_request=SecretPutRequest(secret=secret_message, value=value)
        )
        return self.version(version=response.secret_version.version)
    except GRPCError as grpc_err:
        raise exception_from_grpc_error(grpc_err)
def version(self, version: str)

Return a reference to a specific version of a secret.

Can be used to retrieve the secret value associated with the version.

Expand source code
def version(self, version: str):
    """
    Return a reference to a specific version of a secret.

    Can be used to retrieve the secret value associated with the version.
    """
    return SecretVersion(_secrets=self._secrets, secret=self, id=version)
class SecretValue (version: SecretVersion, value: bytes)

Represents the value of a secret, tied to a specific version.

Expand source code
@dataclass(frozen=True)
class SecretValue(object):
    """Represents the value of a secret, tied to a specific version."""

    # The version containing this value. Never 'latest', always a specific version.
    version: SecretVersion
    value: bytes

    def __str__(self) -> str:
        return self.value.decode("utf-8")

    def __bytes__(self) -> bytes:
        return self.value

    def as_string(self):
        """Return the content of this secret value as a string."""
        return str(self)

    def as_bytes(self):
        """Return the content of this secret value."""
        return bytes(self)

Class variables

var value : bytes
var versionSecretVersion

Methods

def as_bytes(self)

Return the content of this secret value.

Expand source code
def as_bytes(self):
    """Return the content of this secret value."""
    return bytes(self)
def as_string(self)

Return the content of this secret value as a string.

Expand source code
def as_string(self):
    """Return the content of this secret value as a string."""
    return str(self)
class SecretVersion (_secrets: Secrets, secret: SecretContainerRef, id: str)

A reference to a version of a secret, used to access the value of the version.

Expand source code
@dataclass(frozen=True)
class SecretVersion(object):
    """A reference to a version of a secret, used to access the value of the version."""

    _secrets: Secrets
    secret: SecretContainerRef
    id: str

    async def access(self) -> SecretValue:
        """Return the value stored against this version of the secret."""
        version_message = _secret_version_to_wire(self)
        try:
            response = await self._secrets._secrets_stub.access(
                secret_access_request=SecretAccessRequest(secret_version=version_message)
            )
        except GRPCError as grpc_err:
            raise exception_from_grpc_error(grpc_err)

        # Construct a new SecretVersion if the response version id doesn't match this reference.
        # This ensures calls to access from the 'latest' version return new version objects
        # with a fixed version id.
        static_version = (
            self
            if response.secret_version.version == self.id
            else SecretVersion(_secrets=self._secrets, secret=self.secret, id=response.secret_version.version)
        )

        return SecretValue(version=static_version, value=response.value)

Class variables

var id : str
var secretSecretContainerRef

Methods

async def access(self) ‑> SecretValue

Return the value stored against this version of the secret.

Expand source code
async def access(self) -> SecretValue:
    """Return the value stored against this version of the secret."""
    version_message = _secret_version_to_wire(self)
    try:
        response = await self._secrets._secrets_stub.access(
            secret_access_request=SecretAccessRequest(secret_version=version_message)
        )
    except GRPCError as grpc_err:
        raise exception_from_grpc_error(grpc_err)

    # Construct a new SecretVersion if the response version id doesn't match this reference.
    # This ensures calls to access from the 'latest' version return new version objects
    # with a fixed version id.
    static_version = (
        self
        if response.secret_version.version == self.id
        else SecretVersion(_secrets=self._secrets, secret=self.secret, id=response.secret_version.version)
    )

    return SecretValue(version=static_version, value=response.value)
class Secrets

Nitric secrets management client.

This client insulates application code from stack specific secrets managements services.

Construct a Nitric Storage Client.

Expand source code
class Secrets(object):
    """
    Nitric secrets management client.

    This client insulates application code from stack specific secrets managements services.
    """

    def __init__(self):
        """Construct a Nitric Storage Client."""
        self._channel = new_default_channel()
        self._secrets_stub = SecretServiceStub(channel=self._channel)

    def __del__(self):
        # close the channel when this client is destroyed
        if self._channel is not None:
            self._channel.close()

    def secret(self, name: str):
        """Return a reference to a secret container from the connected secrets management service."""
        return SecretContainerRef(_secrets=self, name=name)

Methods

def secret(self, name: str)

Return a reference to a secret container from the connected secrets management service.

Expand source code
def secret(self, name: str):
    """Return a reference to a secret container from the connected secrets management service."""
    return SecretContainerRef(_secrets=self, name=name)