Commit ed5ebdc0 authored by Pierre SMEYERS's avatar Pierre SMEYERS
Browse files

Merge branch 'feature-vault' into 'master'

Support vault variant

Closes #2

See merge request to-be-continuous/robotframework!1
parents 360a7b00 4b398fcf
Loading
Loading
Loading
Loading
+64 −0
Original line number Diff line number Diff line
@@ -146,3 +146,67 @@ stages:
  - publish
  - production
```

## Variants

### Vault variant

This variant allows delegating your secrets management to a [Vault](https://www.vaultproject.io/) server.

#### Configuration

In order to be able to communicate with the Vault server, the variant requires the additional configuration parameters:

| Name              | description                            | default value     |
| ----------------- | -------------------------------------- | ----------------- |
| `VAULT_BASE_URL`  | The Vault server base API url          | _none_ |
| :lock: `VAULT_ROLE_ID`   | The [AppRole](https://www.vaultproject.io/docs/auth/approle) RoleID | **must be defined** |
| :lock: `VAULT_SECRET_ID` | The [AppRole](https://www.vaultproject.io/docs/auth/approle) SecretID | **must be defined** |

> :information_source: 
>
> `VAULT_ROLE_ID` and `VAULT_SECRET_ID` can be replaced by `VAULT_JWT_ROLE` and `VAULT_JWT_TOKEN`. More informations can be found [HERE](https://docs.gitlab.com/ee/ci/examples/authenticating-with-hashicorp-vault/)

#### Usage

Then you may retrieve any of your secret(s) from Vault using the following syntax:

```text
@url@http://vault-secrets-provider/api/secrets/{secret_path}?field={field}
```

With:

| Name                             | description                            |
| -------------------------------- | -------------------------------------- |
| `secret_path` (_path parameter_) | this is your secret location in the Vault server |
| `field` (_query parameter_)      | parameter to access a single basic field from the secret JSON payload |

#### Example

```yaml
include:
  # main template
  - project: 'to-be-continuous/robotframework'
    ref: '1.2.0'
    file: '/templates/gitlab-ci-robotframework.yml'
  # Vault variant
  - project: 'to-be-continuous/robotframework'
    ref: '1.2.0'
    file: '/templates/gitlab-ci-robotframework-vault.yml'

variables:
    ### variables have to be explicitly declared in the YAML to be exported to the service.
    VAULT_ROLE_ID: ${VAULT_ROLE_ID}
    VAULT_SECRET_ID: ${VAULT_SECRET_ID}
    # $VAULT_ROLE_ID and $VAULT_SECRET_ID defined as a secret CI/CD variable

    ### /!\ don't define VAULT_ROLE_ID and VAULT_SECRET_ID if you want to use gitlab JWT
    # VAULT_JWT_TOKEN: "$CI_JOB_JWT"
    # VAULT_JWT_ROLE: "my_gitlab_role"

    ### Secrets managed by Vault
    MY_APPLICATION_PASS: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/robot/noprod?field=application_password"
    VAULT_BASE_URL: "https://vault.acme.host/v1"

```
 No newline at end of file
+26 −0
Original line number Diff line number Diff line
@@ -49,5 +49,31 @@
      "description": "Force to execute the test after each commit (even on development branches)",
      "advanced": true
    }
  ],
  "variants": [
    {
      "id": "vault",
      "name": "Vault",
      "description": "Retrieve secrets from a [Vault](https://www.vaultproject.io/) server",
      "template_path": "templates/gitlab-ci-helm-vault.yml",
      "variables": [
        {
          "name": "VAULT_BASE_URL",
          "description": "The Vault server base API url"
        },
        {
          "name": "VAULT_ROLE_ID",
          "description": "The [AppRole](https://www.vaultproject.io/docs/auth/approle) RoleID",
          "mandatory": true,
          "secret": true
        },
        {
          "name": "VAULT_SECRET_ID",
          "description": "The [AppRole](https://www.vaultproject.io/docs/auth/approle) SecretID",
          "mandatory": true,
          "secret": true
        }
      ]
    }
  ]
}
+12 −0
Original line number Diff line number Diff line
# =====================================================================================================================
# === Vault template variant
# =====================================================================================================================
# Vault variables will have to be explicitly declared in the YAML to be exported to the service, but since we don't
# know which authentication method will be used (AppRole or JWT), that will have to be done by the including project.

.robotframework-base:
  services:
    - name: "$CI_REGISTRY/to-be-continuous/tools/tracking:master"
      command: ["--service", "robotframework", "1.2.0" ]
    - name: "$CI_REGISTRY/to-be-continuous/tools/vault-secrets-provider:master"
      alias: "vault-secrets-provider"
+72 −0
Original line number Diff line number Diff line
@@ -190,6 +190,77 @@ stages:
      log_info "No upstream environment url found: leave default"
    fi
  }
  # evaluate and export a secret
  # - $1: secret variable name
  function eval_secret() {
    name=$1
    value=$(eval echo "\$${name}")
    case "$value" in
    @b64@*)
      decoded=$(mktemp)
      errors=$(mktemp)
      if echo "$value" | cut -c6- | base64 -d > "${decoded}" 2> "${errors}"
      then
        # shellcheck disable=SC2086
        export ${name}="$(cat ${decoded})"
        log_info "Successfully decoded base64 secret \\e[33;1m${name}\\e[0m"
      else
        fail "Failed decoding base64 secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")"
      fi
      ;;
    @hex@*)
      decoded=$(mktemp)
      errors=$(mktemp)
      if echo "$value" | cut -c6- | sed 's/\([0-9A-F]\{2\}\)/\\\\x\1/gI' | xargs printf > "${decoded}" 2> "${errors}"
      then
        # shellcheck disable=SC2086
        export ${name}="$(cat ${decoded})"
        log_info "Successfully decoded hexadecimal secret \\e[33;1m${name}\\e[0m"
      else
        fail "Failed decoding hexadecimal secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")"
      fi
      ;;
    @url@*)
      url=$(echo "$value" | cut -c6-)
      if command -v curl > /dev/null
      then
        decoded=$(mktemp)
        errors=$(mktemp)
        if curl -s -S -f --connect-timeout 5 -o "${decoded}" "$url" 2> "${errors}"
        then
          # shellcheck disable=SC2086
          export ${name}="$(cat ${decoded})"
          log_info "Successfully curl'd secret \\e[33;1m${name}\\e[0m"
        else
          fail "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")"
        fi
      elif command -v wget > /dev/null
      then
        decoded=$(mktemp)
        errors=$(mktemp)
        if wget -T 5 -O "${decoded}" "$url" 2> "${errors}"
        then
          # shellcheck disable=SC2086
          export ${name}="$(cat ${decoded})"
          log_info "Successfully wget'd secret \\e[33;1m${name}\\e[0m"
        else
          fail "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")"
        fi
      else
        fail "Couldn't get secret \\e[33;1m${name}\\e[0m: no http client found"
      fi
      ;;
    esac
  }

  function eval_all_secrets() {
    encoded_vars=$(env | grep -Ev '(^|.*_ENV_)scoped__' | awk -F '=' '/^[a-zA-Z0-9_]*=@(b64|hex|url)@/ {print $1}')
    for var in $encoded_vars
    do
      eval_secret "$var"
    done
  }


  function robot_cli() {
    eval_base_url
@@ -233,6 +304,7 @@ stages:

  if [[ -z "$TEMPLATE_CHECK_UPDATE_DISABLED" ]]; then check_for_update robotframework "1.2.0"; fi
  unscope_variables
  eval_all_secrets

  # ENDSCRIPT