pass-operator


Namepass-operator JSON
Version 0.3.2 PyPI version JSON
download
home_page
SummaryA kubernetes operator that syncs and decrypts secrets from pass git repositories
upload_time2024-02-14 14:54:40
maintainerEmma Doyle
docs_urlNone
authorEmma Doyle
requires_python>=3.10,<4.0
licenseGPL-3.0-or-later
keywords python kubernetes secrets operator pass
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # `pass` secrets operator

A Kubernetes operator to sync and decrypt secrets from a password store ([pass](https://www.passwordstore.org/)) Git repository. This operator is proposed as a proof-of-concept and shouldn't be used in any production capacity.

While this approach to secrets management on Kubernetes is more technically challenging, the advantage is that we don't have to rely on a 3rd party SaaS platform, such as Vault or Doppler, to hold our secrets (the obvious benefits these platforms do provide, however, are better user and access management). We may also use this operator in an airgapped environment with a self-hosted git repository.

<!--
I also acknowledge that this approach swims against the DevSecOps tide in that it requires you to store your secrets (albeit encrypted)
in Git, a practice that is often discouraged and typically forbidden at most organizations.
-->

## How it works

The following flowchart outlines how this operator reacts to `PassSecret`-related events and pass store updates.

<p align="center" width="100%">
  <img width="100%" src="img/pass-operator-flow.png" alt="pass operator flow diagram">
</p>

From a high level, this operator runs `git pull` on an interval to grab updates from a git repository populated with encrypted
secrets by `pass`. It maps secrets' paths to data values through the application of a [`PassSecret`](helm/operator/crds/PassSecret.yaml), a [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/), such as the following.

```yaml
apiVersion: secrets.premiscale.com/v1alpha1
kind: PassSecret
metadata:
  name: mysecret
  namespace: pass-operator-test
spec:
  encryptedData:
    mykey: premiscale/mydata
  managedSecret:
    name: mysecret
    namespace: pass-operator-test
    type: Opaque
    immutable: false
```

The above `PassSecret` manifest translates to the following `Secret`.

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
  namespace: pass-operator-test
stringData:
  mykey: <decrypted contents of premiscale/mydata>
immutable: false
type: Opaque
```

## Use

This operator requires the following items to start successfully.

- private GPG key to decrypt the secrets that have been encrypted with a public key, locally
- local pass store (on your local development machine)
- git repository populated by the local password store
- private SSH key to clone the Git repository

I will go more in-depth and explain these requirements in the following sections.

### Private GPG key

The private GPG key is used by `pass` to decrypt your secrets that were encrypted on your local machine.

<details>
  <summary><b>Generating GPG keys</b></summary>

  You can find a lot of explanation about how to generate keys with GPG online, but I'll write down my process below for generating keys to use with this operator.

  1. First, generate a key.

      ```shell
      $ gpg --generate-key
      gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
      This is free software: you are free to change and redistribute it.
      There is NO WARRANTY, to the extent permitted by law.

      Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

      GnuPG needs to construct a user ID to identify your key.

      Real name: Emma Doyle
      Email address: emma@premiscale.com
      You selected this USER-ID:
      "Emma Doyle <emma@premiscale.com>"

      Change (N)ame, (E)mail, or (O)kay/(Q)uit? O
      We need to generate a lot of random bytes. It is a good idea to perform
      some other action (type on the keyboard, move the mouse, utilize the
      disks) during the prime generation; this gives the random number
      generator a better chance to gain enough entropy.
      We need to generate a lot of random bytes. It is a good idea to perform
      some other action (type on the keyboard, move the mouse, utilize the
      disks) during the prime generation; this gives the random number
      generator a better chance to gain enough entropy.
      gpg: key 4B90DE5D5BF143B8 marked as ultimately trusted
      gpg: revocation certificate stored as '/home/emmadoyle/.gnupg/openpgp-revocs.d/51924ADAFC92656FAFEB672D4B90DE5D5BF143B8.rev'
      public and secret key created and signed.

      pub   rsa3072 2024-01-12 [SC] [expires: 2026-01-11]
            51924ADAFC92656FAFEB672D4B90DE5D5BF143B8
      uid                      Emma Doyle <emma@premiscale.com>
      sub   rsa3072 2024-01-12 [E] [expires: 2026-01-11]

      ```

      > **Important:** if you specify a password for your key, you'll need to specify this password in the Helm values.

      You'll now see your key on your keyring.

      ```shell
      $ gpg --list-keys 51924ADAFC92656FAFEB672D4B90DE5D5BF143B8
      pub   rsa3072 2024-01-12 [SC] [expires: 2026-01-11]
            51924ADAFC92656FAFEB672D4B90DE5D5BF143B8
      uid           [ultimate] Emma Doyle <emma@premiscale.com>
      sub   rsa3072 2024-01-12 [E] [expires: 2026-01-11]
      ```

  2. Export your private key and b64 encode it (otherwise it will dump a bunch of binary data to your shell).

      ```shell
      $ gpg --armor --export-secret-keys 51924ADAFC92656FAFEB672D4B90DE5D5BF143B8 | base64
      ...
      ```

      Copy this value and update your [Helm values](/helm/operator/).

</details>

### Password store

Install [`pass`](https://www.passwordstore.org/) and initialize a local store using the GPG keys you generated in the last step.

```shell
pass init "$GPG_KEY_ID" --path <subpath of ~/.password-store/>
```

Now, on your local machine,

```shell
$ ls -lash ~/.password-store/repo/
total 12K
4.0K drwx------  2 emmadoyle emmadoyle 4.0K Jan 15 13:36 .
4.0K drwxrwxr-x 13 emmadoyle emmadoyle 4.0K Jan 15 13:36 ..
4.0K -rw-------  1 emmadoyle emmadoyle   41 Jan 15 13:36 .gpg-id
```

### Git repository

From the `pass` [man page](https://git.zx2c4.com/password-store/about/),

```text
...
pass git git-command-args...
        If the password store is a git repository, execute a git command
        specified by git-command-args.
...
```

we may easily link our local password store to a remote Git repository. This operator uses `git` alongside `pass` to pull secret updates.

```shell
$ git init ~/.password-store/repo/
$ ls -lash ~/.password-store/repo/
total 16K
4.0K drwx------  3 emmadoyle emmadoyle 4.0K Jan 15 13:38 .
4.0K drwxrwxr-x 13 emmadoyle emmadoyle 4.0K Jan 15 13:36 ..
4.0K drwxrwxr-x  7 emmadoyle emmadoyle 4.0K Jan 15 13:38 .git
4.0K -rw-------  1 emmadoyle emmadoyle   41 Jan 15 13:36 .gpg-id
```

### Private SSH key

Now add a remote git repository and watch as `pass insert`-commands create local commits automatically. Sync your local password store with the remote repo via `pass git push`.

## Development

### Testing

Run unit tests with

```shell
poetry run pytest tests/unit
```

e2e tests against a live environment with

```shell
poetry run pytest tests/e2e
```

And coverage against the codebase with

```shell
poetry run coverage run -m pytest
poetry run coverage report -m pytest
```


            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "pass-operator",
    "maintainer": "Emma Doyle",
    "docs_url": null,
    "requires_python": ">=3.10,<4.0",
    "maintainer_email": "emma@premiscale.com",
    "keywords": "python,kubernetes,secrets,operator,pass",
    "author": "Emma Doyle",
    "author_email": "emma@premiscale.com",
    "download_url": "https://files.pythonhosted.org/packages/7a/0e/898d04a38b1f0d3808d28619182d2b97a22705621a6190ac80fdd8c8423e/pass_operator-0.3.2.tar.gz",
    "platform": null,
    "description": "# `pass` secrets operator\n\nA Kubernetes operator to sync and decrypt secrets from a password store ([pass](https://www.passwordstore.org/)) Git repository. This operator is proposed as a proof-of-concept and shouldn't be used in any production capacity.\n\nWhile this approach to secrets management on Kubernetes is more technically challenging, the advantage is that we don't have to rely on a 3rd party SaaS platform, such as Vault or Doppler, to hold our secrets (the obvious benefits these platforms do provide, however, are better user and access management). We may also use this operator in an airgapped environment with a self-hosted git repository.\n\n<!--\nI also acknowledge that this approach swims against the DevSecOps tide in that it requires you to store your secrets (albeit encrypted)\nin Git, a practice that is often discouraged and typically forbidden at most organizations.\n-->\n\n## How it works\n\nThe following flowchart outlines how this operator reacts to `PassSecret`-related events and pass store updates.\n\n<p align=\"center\" width=\"100%\">\n  <img width=\"100%\" src=\"img/pass-operator-flow.png\" alt=\"pass operator flow diagram\">\n</p>\n\nFrom a high level, this operator runs `git pull` on an interval to grab updates from a git repository populated with encrypted\nsecrets by `pass`. It maps secrets' paths to data values through the application of a [`PassSecret`](helm/operator/crds/PassSecret.yaml), a [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/), such as the following.\n\n```yaml\napiVersion: secrets.premiscale.com/v1alpha1\nkind: PassSecret\nmetadata:\n  name: mysecret\n  namespace: pass-operator-test\nspec:\n  encryptedData:\n    mykey: premiscale/mydata\n  managedSecret:\n    name: mysecret\n    namespace: pass-operator-test\n    type: Opaque\n    immutable: false\n```\n\nThe above `PassSecret` manifest translates to the following `Secret`.\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: mysecret\n  namespace: pass-operator-test\nstringData:\n  mykey: <decrypted contents of premiscale/mydata>\nimmutable: false\ntype: Opaque\n```\n\n## Use\n\nThis operator requires the following items to start successfully.\n\n- private GPG key to decrypt the secrets that have been encrypted with a public key, locally\n- local pass store (on your local development machine)\n- git repository populated by the local password store\n- private SSH key to clone the Git repository\n\nI will go more in-depth and explain these requirements in the following sections.\n\n### Private GPG key\n\nThe private GPG key is used by `pass` to decrypt your secrets that were encrypted on your local machine.\n\n<details>\n  <summary><b>Generating GPG keys</b></summary>\n\n  You can find a lot of explanation about how to generate keys with GPG online, but I'll write down my process below for generating keys to use with this operator.\n\n  1. First, generate a key.\n\n      ```shell\n      $ gpg --generate-key\n      gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.\n      This is free software: you are free to change and redistribute it.\n      There is NO WARRANTY, to the extent permitted by law.\n\n      Note: Use \"gpg --full-generate-key\" for a full featured key generation dialog.\n\n      GnuPG needs to construct a user ID to identify your key.\n\n      Real name: Emma Doyle\n      Email address: emma@premiscale.com\n      You selected this USER-ID:\n      \"Emma Doyle <emma@premiscale.com>\"\n\n      Change (N)ame, (E)mail, or (O)kay/(Q)uit? O\n      We need to generate a lot of random bytes. It is a good idea to perform\n      some other action (type on the keyboard, move the mouse, utilize the\n      disks) during the prime generation; this gives the random number\n      generator a better chance to gain enough entropy.\n      We need to generate a lot of random bytes. It is a good idea to perform\n      some other action (type on the keyboard, move the mouse, utilize the\n      disks) during the prime generation; this gives the random number\n      generator a better chance to gain enough entropy.\n      gpg: key 4B90DE5D5BF143B8 marked as ultimately trusted\n      gpg: revocation certificate stored as '/home/emmadoyle/.gnupg/openpgp-revocs.d/51924ADAFC92656FAFEB672D4B90DE5D5BF143B8.rev'\n      public and secret key created and signed.\n\n      pub   rsa3072 2024-01-12 [SC] [expires: 2026-01-11]\n            51924ADAFC92656FAFEB672D4B90DE5D5BF143B8\n      uid                      Emma Doyle <emma@premiscale.com>\n      sub   rsa3072 2024-01-12 [E] [expires: 2026-01-11]\n\n      ```\n\n      > **Important:** if you specify a password for your key, you'll need to specify this password in the Helm values.\n\n      You'll now see your key on your keyring.\n\n      ```shell\n      $ gpg --list-keys 51924ADAFC92656FAFEB672D4B90DE5D5BF143B8\n      pub   rsa3072 2024-01-12 [SC] [expires: 2026-01-11]\n            51924ADAFC92656FAFEB672D4B90DE5D5BF143B8\n      uid           [ultimate] Emma Doyle <emma@premiscale.com>\n      sub   rsa3072 2024-01-12 [E] [expires: 2026-01-11]\n      ```\n\n  2. Export your private key and b64 encode it (otherwise it will dump a bunch of binary data to your shell).\n\n      ```shell\n      $ gpg --armor --export-secret-keys 51924ADAFC92656FAFEB672D4B90DE5D5BF143B8 | base64\n      ...\n      ```\n\n      Copy this value and update your [Helm values](/helm/operator/).\n\n</details>\n\n### Password store\n\nInstall [`pass`](https://www.passwordstore.org/) and initialize a local store using the GPG keys you generated in the last step.\n\n```shell\npass init \"$GPG_KEY_ID\" --path <subpath of ~/.password-store/>\n```\n\nNow, on your local machine,\n\n```shell\n$ ls -lash ~/.password-store/repo/\ntotal 12K\n4.0K drwx------  2 emmadoyle emmadoyle 4.0K Jan 15 13:36 .\n4.0K drwxrwxr-x 13 emmadoyle emmadoyle 4.0K Jan 15 13:36 ..\n4.0K -rw-------  1 emmadoyle emmadoyle   41 Jan 15 13:36 .gpg-id\n```\n\n### Git repository\n\nFrom the `pass` [man page](https://git.zx2c4.com/password-store/about/),\n\n```text\n...\npass git git-command-args...\n        If the password store is a git repository, execute a git command\n        specified by git-command-args.\n...\n```\n\nwe may easily link our local password store to a remote Git repository. This operator uses `git` alongside `pass` to pull secret updates.\n\n```shell\n$ git init ~/.password-store/repo/\n$ ls -lash ~/.password-store/repo/\ntotal 16K\n4.0K drwx------  3 emmadoyle emmadoyle 4.0K Jan 15 13:38 .\n4.0K drwxrwxr-x 13 emmadoyle emmadoyle 4.0K Jan 15 13:36 ..\n4.0K drwxrwxr-x  7 emmadoyle emmadoyle 4.0K Jan 15 13:38 .git\n4.0K -rw-------  1 emmadoyle emmadoyle   41 Jan 15 13:36 .gpg-id\n```\n\n### Private SSH key\n\nNow add a remote git repository and watch as `pass insert`-commands create local commits automatically. Sync your local password store with the remote repo via `pass git push`.\n\n## Development\n\n### Testing\n\nRun unit tests with\n\n```shell\npoetry run pytest tests/unit\n```\n\ne2e tests against a live environment with\n\n```shell\npoetry run pytest tests/e2e\n```\n\nAnd coverage against the codebase with\n\n```shell\npoetry run coverage run -m pytest\npoetry run coverage report -m pytest\n```\n\n",
    "bugtrack_url": null,
    "license": "GPL-3.0-or-later",
    "summary": "A kubernetes operator that syncs and decrypts secrets from pass git repositories",
    "version": "0.3.2",
    "project_urls": null,
    "split_keywords": [
        "python",
        "kubernetes",
        "secrets",
        "operator",
        "pass"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8b4248be55643ae833c601dbdb358010e92e42bdcff7bdf22dd048fbf3b47b0d",
                "md5": "2355e1dea136faeafa54172c5233e968",
                "sha256": "641b5cf9081c336b9abbbefb3e1c66c70871ea176bbfb4bde9d6d8853f7c608f"
            },
            "downloads": -1,
            "filename": "pass_operator-0.3.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2355e1dea136faeafa54172c5233e968",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10,<4.0",
            "size": 38306,
            "upload_time": "2024-02-14T14:54:38",
            "upload_time_iso_8601": "2024-02-14T14:54:38.708819Z",
            "url": "https://files.pythonhosted.org/packages/8b/42/48be55643ae833c601dbdb358010e92e42bdcff7bdf22dd048fbf3b47b0d/pass_operator-0.3.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7a0e898d04a38b1f0d3808d28619182d2b97a22705621a6190ac80fdd8c8423e",
                "md5": "d6384a1d0a62cfa908c68b70ca090ec1",
                "sha256": "8a3bab1dea3f943519ec589bb671e1fc129adfb0e10124d78413ff98f56f27ac"
            },
            "downloads": -1,
            "filename": "pass_operator-0.3.2.tar.gz",
            "has_sig": false,
            "md5_digest": "d6384a1d0a62cfa908c68b70ca090ec1",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10,<4.0",
            "size": 25882,
            "upload_time": "2024-02-14T14:54:40",
            "upload_time_iso_8601": "2024-02-14T14:54:40.221463Z",
            "url": "https://files.pythonhosted.org/packages/7a/0e/898d04a38b1f0d3808d28619182d2b97a22705621a6190ac80fdd8c8423e/pass_operator-0.3.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-14 14:54:40",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "pass-operator"
}
        
Elapsed time: 0.18118s