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 ```