github.com/zntrio/harp/v2@v2.0.9/FEATURES.md (about)

     1  # Features overview
     2  
     3  - [Features overview](#features-overview)
     4    - [Glossary](#glossary)
     5    - [Bundle management](#bundle-management)
     6      - [Features](#features)
     7      - [Pipelines](#pipelines)
     8    - [Template Engine](#template-engine)
     9      - [Render a template](#render-a-template)
    10      - [Set external values](#set-external-values)
    11      - [Load values from file](#load-values-from-file)
    12      - [Value object debugger](#value-object-debugger)
    13      - [Load from different filetypes](#load-from-different-filetypes)
    14    - [Secret Bundle](#secret-bundle)
    15      - [Create a bundle from template](#create-a-bundle-from-template)
    16      - [Create a bundle from a JSON map](#create-a-bundle-from-a-json-map)
    17      - [Read a secret value](#read-a-secret-value)
    18        - [Example](#example)
    19      - [Patch a bundle](#patch-a-bundle)
    20      - [Calculate a bundle difference](#calculate-a-bundle-difference)
    21      - [Dump a secret bundle](#dump-a-secret-bundle)
    22      - [Encrypt secret values](#encrypt-secret-values)
    23      - [Decrypt secret values](#decrypt-secret-values)
    24      - [Linter / Structure checker](#linter--structure-checker)
    25        - [Check that all packages are CSO compliant](#check-that-all-packages-are-cso-compliant)
    26        - [Validate a secret structure](#validate-a-secret-structure)
    27        - [Generate a ruleset from a bundle](#generate-a-ruleset-from-a-bundle)
    28    - [Secret Container](#secret-container)
    29      - [Seal a secret container](#seal-a-secret-container)
    30        - [Create an identity](#create-an-identity)
    31          - [Use a passphrase as private key protection](#use-a-passphrase-as-private-key-protection)
    32        - [Ephemeral Container Key](#ephemeral-container-key)
    33        - [Deterministic Container Key](#deterministic-container-key)
    34      - [Recover a container key from identity](#recover-a-container-key-from-identity)
    35      - [Unseal a secret container](#unseal-a-secret-container)
    36    - [Vault specific commands](#vault-specific-commands)
    37      - [Export a complete secret backend from Vault](#export-a-complete-secret-backend-from-vault)
    38      - [Import a bundle in a target secret backend in Vault](#import-a-bundle-in-a-target-secret-backend-in-vault)
    39      - [Share simple secret between 2 users](#share-simple-secret-between-2-users)
    40      - [Share a container](#share-a-container)
    41      - [Prepare a secret bundle for an ephemeral worker](#prepare-a-secret-bundle-for-an-ephemeral-worker)
    42      - [Use Vault in-transit key to encrypt a container identity](#use-vault-in-transit-key-to-encrypt-a-container-identity)
    43  
    44  ## Glossary
    45  
    46  * `secret` is a tuple of information identified by a `key` holding a `value`
    47      characterized by an array of bytes.
    48  * `package` is a collection of `secret` kv pair, it is identified by a name
    49      usually called the secret path;
    50  * `bundle` is a collection of `package` items, and form an atomic group of
    51      information representing a secret value state.
    52  * `container/crate` is the file used to securely store the `bundle`.
    53  * `secret operator` is a role assigned to an identity allowed to manage secrets;
    54  * `secret consumer`is a role assigned to an identity allowed to read secrets with
    55      authorized policy;
    56  * `secret backend` is a technical component used to store and organize secrets;
    57  
    58  ## Bundle management
    59  
    60  harp is used by `secret-operators` to manage and produce secret
    61  bundles. It implements secret data management pipelines, to make it auditable
    62  and reproductible.
    63  
    64  ![Secret management Pipeline](docs/harp/img/SM-HARP-PIPELINE.png)
    65  
    66  ### Features
    67  
    68  * Input(s)
    69  
    70    * Read
    71      * `Hashicorp Vault`
    72      * JSON map
    73      * Secret container dump
    74    * Generate
    75      * `BundleTemplate` for secret bootstrap
    76  
    77  * Builtin output(s)
    78    * `Harp Secret Container`
    79    * `Hashicorp Vault`
    80  
    81  ### Pipelines
    82  
    83  `harp` allows you to handle secret using deterministic pipelines expressed
    84  using series of atomic cli operations.
    85  
    86  ![Pipelines](docs/harp/img/SM-HARP.png)
    87  
    88  > The main objective is to reach as soon as possible the harp native
    89  > container to be used by the harp core cli.
    90  > If you need to pull or push secret from / to external secret storage engine,
    91  > just use the SDK to generate a harp plugin to pull secret and store
    92  > them as a harp container.
    93  
    94  ## Template Engine
    95  
    96  The provided template engine is used to describe and implement the value
    97  generation algorithms. You can use it for secret data generation but also for
    98  various other use cases. [Sample use cases](./docs/onboarding/1-template-engine/9-usecases.md)
    99  
   100  As an input you can take almost anything which is a string stream.
   101  
   102  > For more information about the template engine, please read the dedicated
   103  > section - [Template Engine](./docs/onboarding/1-template-engine/1-introduction.md)
   104  
   105  ### Render a template
   106  
   107  `harp` exposes data generation function used to generate the data according to
   108  the specification described by the user.
   109  
   110  > Generate an EC P-256 curve keypair, and output the public key using JWK encoding
   111  
   112  ```sh
   113  echo '{{ $key := cryptoPair "ec:p256" }}{{ $key.Public | toJwk }}' | harp template
   114  {"kty":"EC","kid":"LP2o9bst8ViOGl1q6CIIs23s8g9d6yFAt5iD31qq80w=","crv":"P-256","x":"rIRQRSMbl-DRihd5OGakNWGQcVOsNLICtFfZ5cnJP3U","y":"0OP6GW1Rci4S-0GLtIGbcP3VAPe4lWjwpt8-rZtOvHU"}
   115  ```
   116  
   117  > Generate a strong password and encode it using Base64
   118  
   119  ```sh
   120  $ echo '{{ strongPassword | b64enc }}' | harp template
   121  LzBRTl9OcGY8NCF8NDJVMjQvOjIpbFUxbzQhMUB4alE=
   122  ```
   123  
   124  > Load a secret from a secret storage (Vault or an Harp Container)
   125  
   126  ```sh
   127  $ cat <<EOF | harp template
   128  export APP_INSTANCE_ID={{ .randAlpha 64 }}
   129  {{- with secret "app/production/operations/secops/v1.0.0/harp/external/oidc" }}
   130  export OIDC_SERVER="{{ .authorization_server_url }}"
   131  export OIDC_CLIENT_ID="{{ .client_id }}"
   132  export OIDC_CLIENT_SECRET_KEY="{{ .client_private_key | ba64enc }}"
   133  {{- end }}
   134  EOF
   135  export APP_INSTANCE_ID=AcFGdxktUdQBVDaQiMlEMxHvPgqrTOEtiPYZsWFrbOJIqshShlvIxYGkkhlpcBto
   136  export OIDC_SERVER=https://sso.as.tld
   137  export OIDC_CLIENT_ID=83af60478e3e2a37a1f42b7a89b6494fa4b04bb7aca9c522de8b9a9b87527d6d
   138  export OIDC_CLIENT_SECRET_KEY=eyJrdHkiOiJFQyIsImtpZCI6IkdUTnk5X0NwWklUSngyZmViLVVnc...klQSk1Ya2dEMXoyR25HTGcifQ==
   139  ```
   140  
   141  ### Set external values
   142  
   143  You can inject static value during the template rendering process, by adding `set`
   144  parameters.
   145  
   146  ```sh
   147  $ echo 'Secret for {{ .Values.quality }}' | harp template --set quality=production
   148  Secret for production
   149  ```
   150  
   151  ### Load values from file
   152  
   153  Define a `values.yaml`
   154  
   155  ```yaml
   156  account: security
   157  quality: production
   158  application:
   159      name: harp
   160  ```
   161  
   162  Load it during template rendering
   163  
   164  ```sh
   165  $ echo 'Account {{ .Values.account }}, quality: {{.Values.quality}} for application {{.Values.application.name}}' | harp template --values values.yaml
   166  Account security, quality: production for application harp
   167  ```
   168  
   169  ### Value object debugger
   170  
   171  Sometimes you need to inspect the `Values` object. In order to do that, apply
   172  the same parameters you use for the templae engine to `values` command.
   173  
   174  ```sh
   175  $ harp values --values values.yaml --set quality=staging | jq
   176  {
   177    "account": "security",
   178    "application": {
   179      "name": "harp"
   180    },
   181    "quality": "staging"
   182  }
   183  ```
   184  
   185  ### Load from different filetypes
   186  
   187  By default value file parsers are detected using the file extension. You can
   188  override the used parser when needed and also specificy an new root for parsed
   189  data.
   190  
   191  ```sh
   192  --values=<filename>(:<parser>(:<root>)?)?
   193  ```
   194  
   195  * `path` can be `-` for stdin or a file path
   196  * `format` is used to override used parser deducted from file extension (`yaml`, `json`, `xml`, `toml`, `hocon`, `hcl1`, `hcl2`)
   197  * `prefix` is the name of the root object appended in the final `Values` object
   198  
   199  ```sh
   200  $ curl https://...../ecsecurity.conf | harp values -f -:hocon
   201  {
   202    ...json representation of hocon file...
   203  }
   204  ```
   205  
   206  Load TF scripts as values to reuse maps :
   207  
   208  ```sh
   209  $ harp values
   210    -f infrastructure/common/variables/accounts.tf:hcl2:accounts \
   211    -f application/security.conf:hocon:app \
   212    --set quality=production \
   213    --set-file certificate=ca.pem
   214  {
   215    "accounts": {
   216       ...JSON representation of TF (HCL2) file...
   217    },
   218    "certificate": "...BASE64 encoded file content ...",
   219    "app": {
   220       ...JSON representation of HOCON file...
   221    },
   222    "quality": "production"
   223  }
   224  ```
   225  
   226  ## Secret Bundle
   227  
   228  The `SecretBundle` [object](./docs/onboarding/3-secret-bundle/2-bundle.md) is
   229  used to represent the secret tree mapped using a K/V store.
   230  
   231  ### Create a bundle from template
   232  
   233  You have to create a `BundleTemplate` that will contains all secret generation
   234  specification to generate a `SecretBundle`.
   235  
   236  This specification is embedded in the bundle so that it will be used for secret
   237  rotation based on the specification.
   238  
   239  Given this YAML specificcation :
   240  
   241  <details><summary>infra.yaml</summary>
   242  <p>
   243  
   244  ```yaml
   245  apiVersion: harp.elastic.co/v1
   246  kind: BundleTemplate
   247  meta:
   248      name: "Ecebootstrap"
   249      owner: syseng@elstc.co
   250      description: "ECE Secret Bootstrap"
   251  spec:
   252      selector:
   253          product: "ece"
   254          version: "v1.0.0"
   255      namespaces:
   256          infrastructure:
   257              - provider: "aws"
   258                description: "ESSP AWS Account"
   259                regions:
   260                    - name: "us-east-1"
   261                      services:
   262                          - type: "rds"
   263                            name: "adminconsole"
   264                            description: "PostgreSQL Database used for AdminConsole storage backend"
   265                            secrets:
   266                                - suffix: "accounts/root_credentials"
   267                                  description: "Root Admin Account"
   268                                  template: |
   269                                      {
   270                                        "user": "{{.Provider}}-{{.Account}}-{{.Region}}-dbroot-{{ randAlphaNum 8 }}",
   271                                        "password": "{{ paranoidPassword | b64enc }}"
   272                                      }
   273  
   274                          - type: "mail"
   275                            name: "mailgun"
   276                            description: "Mailgun encryption keys"
   277                            secrets:
   278                                - suffix: "security/signature_keys"
   279                                  description: "Signature keys"
   280                                  template: |
   281                                      {
   282                                          "privateKey": "{{ $sigKey := cryptoPair "rsa" }}{{ $sigKey.Private | jwk | b64enc }}",
   283                                          "publicKey": "{{ $sigKey.Public | jwk | b64enc }}"
   284                                      }
   285                                - suffix: "security/encryption_keys"
   286                                  description: "Encryption keys"
   287                                  template: |
   288                                      {
   289                                          "encryptionKey": "{{ cryptoKey "aes:256" }}"
   290                                      }
   291  ```
   292  
   293  </p>
   294  </details>
   295  
   296  ```sh
   297  harp from template --in spec.yaml --out infra.bundle
   298  ```
   299  
   300  ### Create a bundle from a JSON map
   301  
   302  You can create a `Bundle` using a json map.
   303  
   304  The JSON input must match the following format.
   305  
   306  ```json
   307  {
   308      "path-1": {
   309          "key1": "value1"
   310      },
   311      "path-2": {
   312          "key1": "value1"
   313      }
   314  }
   315  ```
   316  
   317  You can generate a `Bundle` using the following command :
   318  
   319  ```sh
   320  cat << EOF | harp from jsonmap --out json.bundle
   321  {
   322    "platform/production/customer-1/us-east-1/billing/recurly/vendor_api_key": {
   323      "API_KEY": "recurly-foo-api-123456789"
   324    },
   325    "platform/production/customer-1/us-east-1/postgresql/admiconsole/admin_credentials": {
   326      "password": "KmZkZFBXaH5IYzNlR1FFYnxXZVJCUTBvIzBOM0JAITFtYk1malhCRVZoSixmNSxNZXZwVWVnUjpMMDg3QGdFOQ==",
   327      "username": "dbadmin-uKj9BJGO"
   328    },
   329    "platform/production/customer-1/us-east-1/zookeeper/accounts/admin_credentials": {
   330      "password": "Sm55Vnthb3QxMXpESTVtKHVvOEE5aEVVczhEb2gqWnhTP2VuQzc1bFZ7eVlGL1A2YHJPfX5pMUZoS2lsLnJzQg==",
   331      "username": "zkadmin-DDrEQA8i"
   332    }
   333  }
   334  EOF
   335  ```
   336  
   337  ### Read a secret value
   338  
   339  Read a secret value at a given `path`, and optionally extract only the given `field`.
   340  
   341  ```sh
   342  harp bundle read --in unsealed.bundle \
   343      --path <path> \
   344      --field <field>
   345  ```
   346  
   347  > With dump and jq
   348  
   349  ```sh
   350  harp bundle dump --in unsealed.bundle --content-only | jq -r '.["<path>"].<field>' | jq
   351  ```
   352  
   353  #### Example
   354  
   355  ```sh
   356  harp bundle dump --in unsealed.bundle --content-only | jq -r '.["infra/aws/security/global/ec2/default/ssh/ed25519_keys"].privateKey'
   357  ```
   358  
   359  Is equivalent to
   360  
   361  ```sh
   362  harp bundle read --in unsealed.bundle \
   363      --path "infra/aws/security/global/ec2/default/ssh/ed25519_keys" \
   364      --field privateKey
   365  ```
   366  
   367  ### Patch a bundle
   368  
   369  It uses a specification to apply tranformations to the given bundle.
   370  
   371  > Apply transformation according to a strict path matching selector
   372  
   373  <details><summary>postgresql-rotator.yaml</summary>
   374  <p>
   375  
   376  ```yaml
   377  apiVersion: harp.elastic.co/v1
   378  kind: BundlePatch
   379  meta:
   380      name: "postgresql-rotator"
   381      owner: cloud-security@elastic.co
   382      description: "Rotate postgresql password"
   383  spec:
   384      rules:
   385          # Target a precise secret
   386          - selector:
   387                matchPath:
   388                    # Strict match
   389                    strict: "platform/{{.Values.quality}}/{{.Values.account}}/{{.Values.region}}/postgresql/{{.Values.component}}/admin_credentials"
   390  
   391            # Apply this operation on selector matches
   392            package:
   393                # Access data
   394                data:
   395                    # Target an explicit keys only
   396                    kv:
   397                        remove: ["port"]
   398                        add:
   399                            "listener": "5432"
   400                        update:
   401                            "username": "dbuser-{{.Values.component}}-{{ randAlphaNum 8 }}"
   402                            "password": "{{ paranoidPassword | b64enc }}"
   403  ```
   404  
   405  </p>
   406  </details>
   407  
   408  > Apply transformation according to a regex path matching selector
   409  
   410  <details><summary>secret-service-fernet-rotator.yaml</summary>
   411  <p>
   412  
   413  ```yaml
   414  apiVersion: harp.elastic.co/v1
   415  kind: BundlePatch
   416  meta:
   417      name: "fernet-key-rotator"
   418      owner: cloud-security@elastic.co
   419      description: "Rotate or create all fernet key of given bundle"
   420  spec:
   421      rules:
   422          # Object selector
   423          - selector:
   424                # Package path match this regexp
   425                matchPath:
   426                    # Regex match
   427                    regex: ".*"
   428  
   429            # Apply this operation
   430            package:
   431                # On package annotation
   432                annotations:
   433                    # Update annotation value with new secret
   434                    update:
   435                        secret-service.elstc.co/encryptionKey: |-
   436                            {{ cryptoKey "fernet" }}
   437  
   438                # On package data
   439                data:
   440                    # Update annotations
   441                    annotations:
   442                        # Update annotation value with new secret
   443                        update:
   444                            secret-service.elstc.co/encryptionKey: |-
   445                                {{ cryptoKey "fernet" }}
   446  ```
   447  
   448  </p>
   449  </details>
   450  
   451  ```sh
   452  harp from vault \
   453      --path platform/production/customer-1/us-east-1/postgresql/admiconsole/admin_credentials \
   454      | harp bundle patch --spec postgresql-rotator.yaml \
   455          --set quality=production \
   456          --set account=customer-1 \
   457          --set region=us-east-1 \
   458          --set component=adminconsole | harp to vault
   459  ```
   460  
   461  This will pull the given value from vault as a bundle, rotate the targeted
   462  secret according to values and secret path built with them, and then publish
   463  the bundle back to vault.
   464  
   465  ### Calculate a bundle difference
   466  
   467  This is used to generate a diff report from 2 bundles.
   468  
   469  ```sh
   470  $ harp bundle diff --old input.bundle --new other.bundle
   471  [
   472    ...
   473    {
   474      "op": "add",
   475      "type": "secret",
   476      "path": "platform/production/customer1/us-east-1/zookeeper/accounts/admin_credentials#username",
   477      "value": "zkadmin-DRMRnxsY"
   478    },
   479    ...
   480  ]
   481  ```
   482  
   483  > You can generate the `BundlePatch` from the difference
   484  
   485  ```sh
   486  $ harp bundle diff --old input.bundle --new other.bundle --patch
   487  api_version: harp.elastic.co/v1
   488  kind: BundlePatch
   489  meta:
   490    description: Patch generated from oplog
   491    name: autogenerated-patch
   492  spec:
   493    rules:
   494    - package:
   495        remove: true
   496      selector:
   497        matchPath:
   498          strict: app/staging/customer1/ece/v1.0.0/adminconsole/authentication/otp/okta_api_key
   499  ...
   500  ```
   501  
   502  ### Dump a secret bundle
   503  
   504  If you need to inspect internal representation of the bundle, you could use
   505  `dump` action. This will read the bundle and produce a JSON as output.
   506  
   507  > This output is compatible with `from dump` command
   508  
   509  ```sh
   510  $ harp bundle dump --in input.bundle | jq
   511  {
   512      "annotations": {...},
   513      "labels": {...},
   514      "version": 1,
   515      "packages": [
   516          {
   517              "annotations": {...},
   518              "labels": {...},
   519              "name": "secrets/database/postgres.yml",
   520              "secrets": {
   521                  "annotations": {...},
   522                  "labels": {...},
   523                  "data": [
   524                      {
   525                          "key": "URL",
   526                          "type": "string",
   527                          "value": "<base64>"
   528                      }
   529                  ],
   530                  "versions": {...}
   531              }
   532          }
   533      ]
   534      "template": {...}
   535  }
   536  ```
   537  
   538  If you want to export the bundle content, for additionnal operations (jq, diff, etc.)
   539  you can add the `--content-only` flag, it will export the build as a JSON map.
   540  
   541  > This output is **not** compatible with `load` command
   542  
   543  ```sh
   544  $ harp bundle dump --content-only | jq
   545  {
   546    "platform/production/customer-1/us-east-1/billing/recurly/vendor_api_key": {
   547      "API_KEY": "recurly-foo-api-123456789"
   548    },
   549    "platform/production/customer-1/us-east-1/postgresql/admiconsole/admin_credentials": {
   550      "password": "KmZkZFBXaH5IYzNlR1FFYnxXZVJCUTBvIzBOM0JAITFtYk1malhCRVZoSixmNSxNZXZwVWVnUjpMMDg3QGdFOQ==",
   551      "username": "dbadmin-uKj9BJGO"
   552    },
   553    "platform/production/customer-1/us-east-1/zookeeper/accounts/admin_credentials": {
   554      "password": "Sm55Vnthb3QxMXpESTVtKHVvOEE5aEVVczhEb2gqWnhTP2VuQzc1bFZ7eVlGL1A2YHJPfX5pMUZoS2lsLnJzQg==",
   555      "username": "zkadmin-DDrEQA8i"
   556    }
   557  }
   558  ```
   559  
   560  If you want to export the bundle secret paths.
   561  
   562  ```sh
   563  $ harp bundle dump --path-only
   564  platform/production/customer-1/us-east-1/billing/recurly/vendor_api_key
   565  platform/production/customer-1/us-east-1/postgresql/admiconsole/admin_credentials
   566  platform/production/customer-1/us-east-1/zookeeper/accounts/admin_credentials
   567  ```
   568  
   569  ### Encrypt secret values
   570  
   571  In order to protect your unsealed bundle for confidentiality requirements, you
   572  can encrypt secret values.
   573  
   574  Supported encryption:
   575  
   576  * `aes-gcm` (128, 192, 256)
   577  * `aes-siv`, `aes-pmac-siv` (256)
   578  * `chacha20poly1305`, `xchacha20poly1305`
   579  * `secretbox`
   580  * `fernet`
   581  
   582  For this purpose, you have to generate a key using `keygen` subcommands.
   583  
   584  ```sh
   585  $ harp keygen secretbox
   586  secretbox:Vm1xW_Tp6coVww2SRCWBIR3fh77-oZefXsJiuG02LNw=
   587  ```
   588  
   589  Then apply tranformation to secret bundle
   590  
   591  ```sh
   592  harp bundle encrypt --in unsealed.bundle --out encrypted.bundle \
   593      --key secretbox:Vm1xW_Tp6coVww2SRCWBIR3fh77-oZefXsJiuG02LNw=
   594  ```
   595  
   596  You can still read all keys of the bundle, but secret values attached are now
   597  `locked`.
   598  
   599  ```sh
   600  $ harp bundle dump --in encrypted.bundle | jq
   601  {
   602      "packages": [
   603          {
   604              "namespace":"foo",
   605              "name": "secrets/database/postgres.yml",
   606              "locked": "<base64>"
   607          }
   608      ]
   609      "template": {...}
   610  }
   611  ```
   612  
   613  ### Decrypt secret values
   614  
   615  ```sh
   616  harp bundle decrypt --in encrypted.bundle --out decrypted.bundle \
   617      --key secretbox:Vm1xW_Tp6coVww2SRCWBIR3fh77-oZefXsJiuG02LNw=
   618  ```
   619  
   620  ### Linter / Structure checker
   621  
   622  #### Check that all packages are CSO compliant
   623  
   624  ```yaml
   625  apiVersion: harp.elastic.co/v1
   626  kind: RuleSet
   627  meta:
   628      name: harp-server
   629      description: Package and secret constraints for harp-server
   630      owner: security@elastic.co
   631  spec:
   632      rules:
   633          - name: HARP-SRV-0001
   634            description: All package paths must be CSO compliant
   635            path: "*"
   636            constraints:
   637                - p.is_cso_compliant()
   638  ```
   639  
   640  Lint an empty bundle will raise an error.
   641  
   642  ```sh
   643  $ echo '{}' | harp from jsonmap \
   644    | harp bundle lint --spec test/fixtures/ruleset/valid/cso.yaml
   645  {"level":"fatal","@timestamp":"2021-02-23T10:24:45.852Z","@caller":"cobra@v1.1.3/command.go:856","@message":"unable to execute task","@appName":"harp-bundle-lint","@version":"","@revision":"8ebf40d","@appID":"BfGZbI8QYmSaXsBMWj8j0EASE67QcoP4OnC8nLl8xSXXtsY3PFEaABdfvm6c9yb3","@fields":{"error":"unable to validate given bundle: rule 'HARP-SRV-0001' didn't match any packages"}}
   646  ```
   647  
   648  Lint valid bundle
   649  
   650  ```sh
   651  $ echo '{"infra/aws/security/eu-central-1/ec2/ssh/default/authorized_keys":{"admin":"..."}}' \
   652    | harp from jsonmap \
   653    | harp bundle lint --spec test/fixtures/ruleset/valid/cso.yaml
   654  ```
   655  
   656  > No output and exit code (0) when everything is ok
   657  
   658  #### Validate a secret structure
   659  
   660  ```yaml
   661  apiVersion: harp.elastic.co/v1
   662  kind: RuleSet
   663  meta:
   664      name: harp-server
   665      description: Package and secret constraints for harp-server
   666      owner: security@elastic.co
   667  spec:
   668      rules:
   669          - name: HARP-SRV-0002
   670            description: Database credentials
   671            path: "app/qa/security/harp/v1.0.0/server/database/credentials"
   672            constraints:
   673                - p.has_all_secrets(['DB_HOST','DB_NAME','DB_USER','DB_PASSWORD'])
   674  ```
   675  
   676  Lint an empty bundle will raise an error.
   677  
   678  ```sh
   679  $ echo '{}' | harp from jsonmap \
   680    | harp bundle lint --spec test/fixtures/ruleset/valid/database-secret-validator.yaml
   681  {"level":"fatal","@timestamp":"2021-02-23T10:31:05.792Z","@caller":"cobra@v1.1.3/command.go:856","@message":"unable to execute task","@appName":"harp-bundle-lint","@version":"","@revision":"8ebf40d","@appID":"2kl6OWqgNTHkBumvlEtelxpJ4V1uDQCtE5MlOS1hXaUbOYtU1rrXbEL2zswx65y4","@fields":{"error":"unable to validate given bundle: rule 'HARP-SRV-0002' didn't match any packages"}}
   682  ```
   683  
   684  Lint an invalid bundle
   685  
   686  ```sh
   687  echo '{"app/qa/security/harp/v1.0.0/server/database/credentials":{}}' \
   688    | harp from jsonmap \
   689    | harp bundle lint --spec test/fixtures/ruleset/valid/database-secret-validator.yaml
   690  {"level":"fatal","@timestamp":"2021-02-23T10:31:24.287Z","@caller":"cobra@v1.1.3/command.go:856","@message":"unable to execute task","@appName":"harp-bundle-lint","@version":"","@revision":"8ebf40d","@appID":"7pflS7bCAAsDcAiPJWm36pypWY3nHhqOQwCc9Vp1ABCm8ZUWbmGinGL5zbP1EWvn","@fields":{"error":"unable to validate given bundle: package 'app/qa/security/harp/v1.0.0/server/database/credentials' doesn't validate rule 'HARP-SRV-0002'"}}
   691  ```
   692  
   693  #### Generate a ruleset from a bundle
   694  
   695  It will use the input bundle structure to generate a `RuleSet`.
   696  
   697  ```sh
   698  harp ruleset from-bundle --in customer.bundle
   699  ```
   700  
   701  <details><summary>RuleSet</summary>
   702  
   703  ```yaml
   704  api_version: harp.elastic.co/v1
   705  kind: RuleSet
   706  meta:
   707      description: Generated from bundle content
   708      name: vjz70BPFJuQhm_7quRGNt1ybocQU6DeXCn8h1o4aPm80CI4pM8lNwVBTDqH8SpW0W1r-8dXSVQK67pO-vtgS_Q
   709  spec:
   710      rules:
   711          - constraints:
   712                - p.has_secret("API_KEY")
   713            name: LINT-vjz70B-1
   714            path: app/production/customer1/ece/v1.0.0/adminconsole/authentication/otp/okta_api_key
   715          - constraints:
   716                - p.has_secret("host")
   717                - p.has_secret("port")
   718                - p.has_secret("options")
   719                - p.has_secret("username")
   720                - p.has_secret("password")
   721                - p.has_secret("dbname")
   722            name: LINT-vjz70B-2
   723            path: app/production/customer1/ece/v1.0.0/adminconsole/database/usage_credentials
   724          - constraints:
   725                - p.has_secret("cookieEncryptionKey")
   726                - p.has_secret("sessionSaltSeed")
   727                - p.has_secret("jwtHmacKey")
   728            name: LINT-vjz70B-3
   729            path: app/production/customer1/ece/v1.0.0/adminconsole/http/session
   730          - constraints:
   731                - p.has_secret("API_KEY")
   732            name: LINT-vjz70B-4
   733            path: app/production/customer1/ece/v1.0.0/adminconsole/mailing/sender/mailgun_api_key
   734          - constraints:
   735                - p.has_secret("emailHashPepperSeedKey")
   736            name: LINT-vjz70B-5
   737            path: app/production/customer1/ece/v1.0.0/adminconsole/privacy/anonymizer
   738          - constraints:
   739                - p.has_secret("host")
   740                - p.has_secret("port")
   741                - p.has_secret("options")
   742                - p.has_secret("username")
   743                - p.has_secret("password")
   744                - p.has_secret("dbname")
   745            name: LINT-vjz70B-6
   746            path: app/production/customer1/ece/v1.0.0/userconsole/database/usage_credentials
   747          - constraints:
   748                - p.has_secret("privateKey")
   749                - p.has_secret("publicKey")
   750            name: LINT-vjz70B-7
   751            path: app/production/customer1/ece/v1.0.0/userconsole/http/certificate
   752          - constraints:
   753                - p.has_secret("cookieEncryptionKey")
   754                - p.has_secret("sessionSaltSeed")
   755                - p.has_secret("jwtHmacKey")
   756            name: LINT-vjz70B-8
   757            path: app/production/customer1/ece/v1.0.0/userconsole/http/session
   758          - constraints:
   759                - p.has_secret("user")
   760                - p.has_secret("password")
   761            name: LINT-vjz70B-9
   762            path: infra/aws/essp-customer1/us-east-1/rds/adminconsole/accounts/root_credentials
   763          - constraints:
   764                - p.has_secret("API_KEY")
   765                - p.has_secret("ca.pem")
   766            name: LINT-vjz70B-10
   767            path: platform/production/customer1/us-east-1/billing/recurly/vendor_api_key
   768          - constraints:
   769                - p.has_secret("username")
   770                - p.has_secret("password")
   771            name: LINT-vjz70B-11
   772            path: platform/production/customer1/us-east-1/postgresql/admiconsole/admin_credentials
   773          - constraints:
   774                - p.has_secret("username")
   775                - p.has_secret("password")
   776            name: LINT-vjz70B-12
   777            path: platform/production/customer1/us-east-1/zookeeper/accounts/admin_credentials
   778          - constraints:
   779                - p.has_secret("privateKey")
   780                - p.has_secret("publicKey")
   781            name: LINT-vjz70B-13
   782            path: product/ece/v1.0.0/artifact/signature/key
   783  ```
   784  
   785  </details>
   786  
   787  ## Secret Container
   788  
   789  The `Secret Container` act as the secret storage used to store securely a
   790  `SecretBundle`. It is used to transport information from container producer to
   791  consumers inside the pipeline. It can be used to export a `SecretBundle` and
   792  exposed via secret consuming protocol (Vault, HTPP, gRPC) using the appropriate
   793  plugin.
   794  
   795  ### Seal a secret container
   796  
   797  A container must be sealed to keep its confidentiality and integrity property.
   798  It can be sealed by multiple identities which allow them to unseal it to be able
   799  to read the inner `SecretBundle` object.
   800  
   801  #### Create an identity
   802  
   803  Identities are cryptographic keypairs (Curve25519) used for sealing process.
   804  
   805  The secret container allows sealing to use multiple identities (public keys) during
   806  the process so that these identities matching private keys could be used to unseal
   807  the secret container.
   808  
   809  ##### Use a passphrase as private key protection
   810  
   811  Generate a passphrase first. This passphrase will be used to encrypt the
   812  private key of the identity.
   813  
   814  ```sh
   815  harp passphrase > passphrase.txt
   816  ```
   817  
   818  > Passphrase must be stored and permissionned effeciently in your secret storage.
   819  
   820  Create a recovery identity :
   821  
   822  ```sh
   823  harp container identity \
   824      --passphrase $(cat passphrase.txt) \
   825      --description "Recovery" \
   826      --out recovery.json
   827  ```
   828  
   829  Sample identity
   830  
   831  ```json
   832  {
   833      "@apiVersion": "harp.elastic.co/v1",
   834      "@kind": "ContainerIdentity",
   835      "@timestamp": "2021-02-16T10:06:31.126671Z",
   836      "@description": "recovery",
   837      "public": "recovery1jjq095c68kjz4e3ck5cvu97qrgf8npm7ck2qfex24nw7zfk2g5jqxkzzwt",
   838      "private": {
   839          "encoding": "jwe",
   840          "content": "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjUwMDAwMSwicDJzIjoiVUVVNFdIbHFRMGxEYjI1dWRHWnJiZyJ9.d4qhmOsCNseGI_oyTvOKP6LVdOfEYdKkoplZ0kZuDA1ncUjaKoZOvw.3DmFEueug6zvNkbC.5mvVIkFEBQf9GQulf6BL4TeMfMJcSxQI3sJx3lo0Cf7EJ6ZF1v1U3YaQMB7smG3t9emZNvij5FI8g0DwPd0NHT4BNwuG_-oSbdmHZyD4ilkMdAZYHO9ZctNjLS-0dqV1wG7-uiF40g8FKZbx8UbQ9NDd5UutUTIWfaf8FxhYaf4.xIIn95CNXWAFQd2QCg-tiA"
   841      }
   842  }
   843  ```
   844  
   845  > Recovery identity can be "publicly" stored.
   846  
   847  #### Ephemeral Container Key
   848  
   849  For immutability principle, the sealing process generates a new Container Key
   850  at each execution. It means that all the container consumers must know the new
   851  container to be able to unseal it.
   852  
   853  In order to `seal` a secret container, you can use the following commands :
   854  
   855  Seal the container using the generated passphrase for recovery :
   856  
   857  ```sh
   858  $ harp container seal \
   859      --identity $(cat recovery.json | jq -r ".public") \
   860      --in unsealed.container \
   861      --out sealed.container
   862  Container Key: .....
   863  ```
   864  
   865  #### Deterministic Container Key
   866  
   867  Seal the container using a deterministic container key derived from a master key.
   868  This will prevent modification of container consumers after each container seals.
   869  
   870  Generate a master key :
   871  
   872  > Keep this key as an high sensitive secret.
   873  
   874  ```sh
   875  harp keygen master-key > master.key
   876  ```
   877  
   878  Seal the secret container using deterministic container key derivation (DCKD) :
   879  
   880  ```sh
   881  $ harp container seal \
   882      --identity $(cat recovery.json | jq -r ".public") \
   883      --dckd-master-key $(cat master.key) \
   884      --dckd-target "customer-1:release-XXX:2020-10-31" \
   885      --in unsealed.container \
   886      --out sealed.container
   887  Container key : ....
   888  ```
   889  
   890  * The `dckd-master-key` flag defines the root key used for derivation.
   891  * The `dckd-target` flag defines an arbitry string acting as a salt for Key
   892      Derivation Function.
   893  
   894  ### Recover a container key from identity
   895  
   896  When the container key is lost, you can use attached one of identity private keys
   897  to unseal the container.
   898  
   899  For passphrase recovery :
   900  
   901  ```sh
   902  $ harp container recover --identity recovery.json --passphrase $(cat passphrase.txt)
   903  Container key : mPjzX1A5PcGtZ0nacxkhjl0pZE8XYw84KYF5NO6jhVA
   904  ```
   905  
   906  For Vault recovery :
   907  
   908  ```sh
   909  harp container recover --vault-transit-key harp --identity recovery.json
   910  Container key : VyEJ6lMy7CPOjJnPYMjH-M7uWUym5utYo4JDVNPPMc8
   911  ```
   912  
   913  ### Unseal a secret container
   914  
   915  In order to modify a bundle, this bundle need to be unsealed.
   916  
   917  ```sh
   918  $ harp container seal --in sealed.bundle --out secret.bundle
   919  Enter container key:
   920  ```
   921  
   922  ## Vault specific commands
   923  
   924  ### Export a complete secret backend from Vault
   925  
   926  > Only secrets visible to you will be exported. Also this a CPU/Network intensive
   927  > operation, be aware of it.
   928  
   929  This will be used to export all secrets from a Vault secret engine K/V backend in
   930  a unsealed bundle.
   931  
   932  ```sh
   933  harp from vault \
   934      --path infra \
   935      --path platform \
   936      --path product \
   937      --path app \
   938      --path artifact \
   939      --out vault-backup.bundle
   940  ```
   941  
   942  Or you can pass path via `stdin` or `file` with `--pass-from` flag:
   943  
   944  ```sh
   945  harp bundle dump --path-only --in vault.bundle \
   946      | harp from vault \
   947          --out imported.bundle \
   948          --paths-from -
   949  ```
   950  
   951  ### Import a bundle in a target secret backend in Vault
   952  
   953  This will be used to import an unsealed bundle into a given Vault K/V backend path.
   954  
   955  ```sh
   956  harp to vault \
   957      --in infra.bundle \
   958      --prefix legacy
   959  ```
   960  
   961  ### Share simple secret between 2 users
   962  
   963  User-A:
   964  
   965  ```sh
   966  # Login to your Vault
   967  $ export VAULT_ADDR="...."
   968  $ export VAULT_TOKEN="..."
   969  $ echo -n "my-secret-value" | harp share put
   970  Token : s.MEc2fYXrzDkUCBzLOcGbIGbK (Expires in 30 seconds)
   971  ```
   972  
   973  Send `<token>` to User-B via untrusted communication channels (email, slack, ...)
   974  
   975  ```sh
   976  $ harp share get --token=s.MEc2fYXrzDkUCBzLOcGbIGbK
   977  my-secret-value
   978  ```
   979  
   980  ### Share a container
   981  
   982  Create a bundle from a template and push it in Vault CubbyHole for 15 minutes.
   983  
   984  ```sh
   985  $ harp from bundle-template \
   986       --in samples/customer-bundle/spec.yaml \
   987       --values samples/customer-bundle/values.yaml \
   988       --set quality=production \
   989       | harp share put --ttl 15m --json | jq -r ".token"
   990  s.UHd8E1h5UELiqjwC4CzaQ3l3
   991  ```
   992  
   993  On consumer side
   994  
   995  ```sh
   996  $ harp share get --token=s.UHd8E1h5UELiqjwC4CzaQ3l3 | harp bundle dump --path-only
   997  app/production/customer1/ece/v1.0.0/adminconsole/authentication/otp/okta_api_key
   998  app/production/customer1/ece/v1.0.0/adminconsole/database/usage_credentials
   999  ...
  1000  platform/production/customer1/us-east-1/zookeeper/accounts/admin_credentials
  1001  product/ece/v1.0.0/artifact/signature/key
  1002  ```
  1003  
  1004  ### Prepare a secret bundle for an ephemeral worker
  1005  
  1006  Prepare a list of secret paths required by the job (AdminConsole API Key Rotator)
  1007  
  1008  ```txt
  1009  app/production/customer1/ece/v1.0.0/adminconsole/authentication/otp/okta_api_key
  1010  app/production/customer1/ece/v1.0.0/adminconsole/mailing/sender/mailgun_api_key
  1011  ```
  1012  
  1013  Prepare the content to share
  1014  
  1015  ```sh
  1016  $ harp from vault --paths-from list.txt | harp bundle dump --content-only | jq
  1017  {
  1018    "app/production/customer1/ece/v1.0.0/adminconsole/authentication/otp/okta_api_key": {
  1019      "API_KEY": "okta-foo-api-123456789"
  1020    },
  1021    "app/production/customer1/ece/v1.0.0/adminconsole/mailing/sender/mailgun_api_key": {
  1022      "API_KEY": "mg-admin-9875s-sa"
  1023    }
  1024  }
  1025  ```
  1026  
  1027  (OPTION) Encrypt the bundle before sharing it via Vault CubbyHole
  1028  
  1029  > Asymmetric encryption will be better suited for this use case, but it's not available yet.
  1030  
  1031  ```sh
  1032  $ export PSK=$(harp keygen chacha)
  1033  $ harp from vault --paths-from list.txt \
  1034     | harp bundle encrypt --key=$PSK \
  1035     | harp share put --ttl 15m
  1036  Token : s.R8SizZuS2oqCVKPGra2UieiG (Expires in 900 seconds)
  1037  ```
  1038  
  1039  On the consumer side
  1040  
  1041  ```sh
  1042  $ harp share get --token=s.R8SizZuS2oqCVKPGra2UieiG \
  1043     | harp bundle decrypt --key=$PSK \
  1044     | harp bundle dump --content-only \
  1045     | jq
  1046  {
  1047    "app/production/customer1/ece/v1.0.0/adminconsole/authentication/otp/okta_api_key": {
  1048      "API_KEY": "okta-foo-api-123456789"
  1049    },
  1050    "app/production/customer1/ece/v1.0.0/adminconsole/mailing/sender/mailgun_api_key": {
  1051      "API_KEY": "mg-admin-9875s-sa"
  1052    }
  1053  }
  1054  ```
  1055  
  1056  ### Use Vault in-transit key to encrypt a container identity
  1057  
  1058  > This will remove the passphrase usage, and transform the permission to unseal
  1059  > a container by adding/removing permission to use the transit backend key.
  1060  
  1061  Create a Vault in-transit backend key first.
  1062  
  1063  ```sh
  1064  $ vault secrets enable transit
  1065  Success! Enabled the transit secrets engine at: transit/
  1066  ```
  1067  
  1068  Create a transit encryption key identity :
  1069  
  1070  ```sh
  1071  $ vault write -f transit/keys/harp
  1072  Success! Data written to: transit/keys/harp
  1073  ```
  1074  
  1075  Create a recovery identity :
  1076  
  1077  ```sh
  1078  $ export VAULT_ADDR=...
  1079  $ export VAULT_TOKEN=...
  1080  $ harp container identity \
  1081      --vault-transit-key harp \
  1082      --description "Recovery" \
  1083      --out recovery.json
  1084  ```
  1085  
  1086  Sample identity
  1087  
  1088  ```json
  1089  {
  1090      "@apiVersion": "harp.elastic.co/v1",
  1091      "@kind": "ContainerIdentity",
  1092      "@timestamp": "2020-10-27T19:58:51.398987Z",
  1093      "@description": "Recovery",
  1094      "public": "4hHPpiJMnVhQFlnveRKeCdaPoqHzW74Rro0S1X33QS4",
  1095      "private": {
  1096          "encoding": "kms:vault:q6pcgHWM6wSJWG6OYmHM97DMMeerqTXExYAolfhn4N8",
  1097          "content": "vault:v1:CNMnI9sIRYYD6pRl1TQ3KHHO+JCmfZiAtD+XBnnIxHt4F6CeFYmuUtY6k7+XAMxtAWG5NtLgS0uyPken2ef1ihJ6Pf6DOtlgDUCnDKyVEvGeZcdOdaZHTgc3YIX/wDY9odmtUvjJGaPNMtIADtMPcjkOLgZH3FnF701dJcsKPxr1fqTQd8mCiFFWqWF9kOQYMqf/1yBybcY6XOGI"
  1098      }
  1099  }
  1100  ```