Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add vault-kv interface #97

Merged
merged 13 commits into from
Sep 13, 2023
Merged
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ To quickly get started, see the [template interface](https://github.com/canonica
| Security | [`mutual_tls`](interfaces/mutual_tls/v0/README.md) | ![Status: Draft](https://img.shields.io/badge/Status-Draft-orange) |
| | [`tls_certificates/v0`](interfaces/tls_certificates/v0/README.md) | ![Status: Live](https://img.shields.io/badge/Status-Live-darkgreen) |
| | [`tls_certificates/v1`](interfaces/tls_certificates/v1/README.md) | ![Status: Draft](https://img.shields.io/badge/Status-Draft-orange) |
| | [`vault-kv`](interfaces/vault_kv/v0/README.md) | ![Status: Draft](https://img.shields.io/badge/Status-Draft-orange) |
| Metadata | [`k8s-service`](interfaces/k8s-service/v0/README.md) | ![Status: Draft](https://img.shields.io/badge/Status-Draft-orange) |
| Storage | [`s3`](interfaces/s3/v0/README.md) | ![Status: Draft](https://img.shields.io/badge/Status-Draft-orange) |

Expand Down
52 changes: 52 additions & 0 deletions docs/json_schemas/vault_kv/v0/provider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"title": "ProviderSchema",
"description": "The schema for the provider side of this interface.",
"type": "object",
"properties": {
"unit": {
"$ref": "#/definitions/BaseModel"
},
"app": {
"$ref": "#/definitions/VaultKvProviderSchema"
}
},
"required": [
"app"
],
"definitions": {
"BaseModel": {
"title": "BaseModel",
"type": "object",
"properties": {}
},
"VaultKvProviderSchema": {
"title": "VaultKvProviderSchema",
"type": "object",
"properties": {
"vault_url": {
"title": "Vault Url",
"description": "The URL of the Vault server to connect to.",
"type": "string"
},
"mount": {
"title": "Mount",
"description": "The KV mount available for the requirer application, respecting the pattern 'charm-<requirer app>-<user provided suffix>'.",
"type": "string"
},
"credentials": {
"title": "Credentials",
"description": "Mapping of unit name and credentials for that unit. Credentials are a juju secret containing a 'role-id' and a 'role-secret-id'.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"vault_url",
"mount",
"credentials"
]
}
}
}
46 changes: 46 additions & 0 deletions docs/json_schemas/vault_kv/v0/requirer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"title": "RequirerSchema",
"description": "The schema for the requirer side of this interface.",
"type": "object",
"properties": {
"unit": {
"$ref": "#/definitions/UnitVaultKvRequirerSchema"
},
"app": {
"$ref": "#/definitions/AppVaultKvRequirerSchema"
}
},
"required": [
"unit",
"app"
],
"definitions": {
"UnitVaultKvRequirerSchema": {
"title": "UnitVaultKvRequirerSchema",
"type": "object",
"properties": {
"egress_subnet": {
"title": "Egress Subnet",
"default": "Egress subnet to use, in CIDR notation.",
"type": "string"
},
"nonce": {
"title": "Nonce",
"default": "Uniquely identifying value for this unit. `secrets.token_hex(16)` is recommended.",
"type": "string"
}
}
},
"AppVaultKvRequirerSchema": {
"title": "AppVaultKvRequirerSchema",
"type": "object",
"properties": {
"mount_suffix": {
"title": "Mount Suffix",
"default": "Suffix to append to the mount name to get the KV mount.",
"type": "string"
}
}
}
}
}
67 changes: 67 additions & 0 deletions interfaces/vault_kv/v0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# `vault-kv`

## Usage

Some charms require a secure key value store. This relation interface describes the expected behavior of any charm claiming to interact with Vault Key Value stores.

## Direction

```mermaid
flowchart TD
Requirer -- mount_suffix, nonce, egress_subnet --> Provider
Provider -- vault_url, mount, credentials --> Requirer
```

## Behavior

Both the Requirer and the Provider need to adhere to criteria to be considered compatible with the interface.

### Provider

Provider expectations

- Is expected to provide the vault url
- Is expected to provide a key value mount, the mount name shall respect the following pattern: `charm-<requirer app>-<requirer provided suffix>`
- Is expected to create an approle restricted to the requiring unit's egress subnet.
gboutry marked this conversation as resolved.
Show resolved Hide resolved
- Is expected to create a Juju secret containing a role-id and role-secret-id for each unit
- Is expected to provide the Juju secret ID in the relation data, identified by the unit's nonce.
- Is expected to have out of date credentials when requirer unit's identity change, for some unspecified amount of time
until new credentials have been generated. For example, during an upgrade-charm event.

### Requirer

Requirer expectations

- Is expected to provide a mount suffix
- Is expected to provide an egress subnet for each unit requiring access to the vault key value store.
The unit's egress_subnet shall be used to restrict access to the secret backend.
- Is expected to provide a nonce, i.e. a string uniquely identifying the unit.

## Relation Data

[\[Pydantic Schema\]](./schema.py)

#### Example

```yaml
provider:
app:
vault_url: http://10.152.183.104:8200
mount: charm-barbican-secrets # in case of CMR, mount will look like `charm-remote-fd7bc6a8c2d54d748ec3822da5abf0bc-secrets`
credentials: |
gruyaume marked this conversation as resolved.
Show resolved Hide resolved
{
"3081279da89c48a32923473c2c587019": "secret://4f7cc474-a23d-49a2-8b6e-9835c1e08325/cjk5slcrl3uc767oebp0",
"b49e6098f245344f1035c3aa0e0c9181": "secret://4f7cc474-a23d-49a2-8b6e-9835c1e08325/cjk5slcrl3uc767oebpg"
}
unit: {}
requirer:
app:
mount_suffix: secrets
unit:
barbican-0:
egress_subnet: 10.1.166.206/32
nonce: 3081279da89c48a32923473c2c587019
barbican-1:
egress_subnet: 10.1.166.230/32
nonce: b49e6098f245344f1035c3aa0e0c9181
```
5 changes: 5 additions & 0 deletions interfaces/vault_kv/v0/charms.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
providers: []

# list of charms using this interface as requirers.
# same structure as providers
requirers: []
52 changes: 52 additions & 0 deletions interfaces/vault_kv/v0/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""This file defines the schemas for the provider and requirer sides of this relation interface.

It must expose two interfaces.schema_base.DataBagSchema subclasses called:
- ProviderSchema
- RequirerSchema
"""

from typing import Mapping

from pydantic import BaseModel, Field, Json

from interface_tester.schema_base import DataBagSchema


class VaultKvProviderSchema(BaseModel):
vault_url: str = Field(description="The URL of the Vault server to connect to.")
mount: str = Field(
description=(
"The KV mount available for the requirer application, "
"respecting the pattern 'charm-<requirer app>-<user provided suffix>'."
)
)
credentials: Json[Mapping[str, str]] = Field(
description=(
"Mapping of unit name and credentials for that unit."
" Credentials are a juju secret containing a 'role-id' and a 'role-secret-id'."
)
)


class AppVaultKvRequirerSchema(BaseModel):
mount_suffix: str = Field("Suffix to append to the mount name to get the KV mount.")


class UnitVaultKvRequirerSchema(BaseModel):
egress_subnet: str = Field("Egress subnet to use, in CIDR notation.")
nonce: str = Field(
"Uniquely identifying value for this unit. `secrets.token_hex(16)` is recommended."
)


class ProviderSchema(DataBagSchema):
"""The schema for the provider side of this interface."""

app: VaultKvProviderSchema


class RequirerSchema(DataBagSchema):
"""The schema for the requirer side of this interface."""

app: AppVaultKvRequirerSchema
unit: UnitVaultKvRequirerSchema