F5Networks / f5-bigip-runtime-init

Apache License 2.0
14 stars 15 forks source link

RFE: add cloud provider sdk methods to fetch objects (in addition to type=url) #37

Open amolari opened 2 years ago

amolari commented 2 years ago

The url type property (in runtime_parameters, extension_services, post_onboard_enables, pre_onboard_enable) results in a simple HTTP get request to fetch scripts, parameters or declarations. I'm asking for the possibility, in addition, to use the cloud-vendor (through its sdk) methods to fetch the refered objects.

Example: with AWS, have a s3 type property with the bucket+key information as parameters. The request to fetch the object will be executed with, for ex. the s3.getObject method.

Goal: be able to place those scripts, parameters or declarations on a bucket and comply to stricter enterprise policies, such as

With the actual code, AFAIK it's not possible to satisfy those requirements Note: I refer to AWS just because of my prio use-case. However, it makes sense to do the same for all cloud providers.

shyawnkarim commented 2 years ago

Thanks for your request. We are now tracking this internally with ID ESECLDTPLT-3051.

mikeshimkus commented 2 years ago

Hi @amolari, this RFE is still in our backlog, but here are examples for all three clouds that will let you download and install AT packages from storage using IAM tokens with the current code:

# aws
controls:
  logLevel: info
  logFilename: /var/log/cloud/bigIpRuntimeInit.log
pre_onboard_enabled: []
runtime_parameters:
  - name: INSTANCE_PROFILE
    type: url
    value: 'http://169.254.169.254/latest/meta-data/iam/security-credentials/'
  - name: TEMPORARY_CREDENTIALS
    type: url
    value: 'http://169.254.169.254/latest/meta-data/iam/security-credentials/{{{INSTANCE_PROFILE}}}'
bigip_ready_enabled:
  - name: download_packages
    type: inline
    commands:
      - "date=\"`date +'%a, %d %b %Y %H:%M:%S %z'`\"; signature_string=\"GET\n\n\n${date}\nx-amz-security-token:{{{TEMPORARY_CREDENTIALS.Token}}}\n/<myBucketName>/<myKey>/f5-appsvcs-templates-1.16.0-1.noarch.rpm\"; signature=`/bin/echo -en \"${signature_string}\" | openssl sha1 -hmac {{{TEMPORARY_CREDENTIALS.SecretAccessKey}}} -binary | base64`; authorization=\"AWS {{{TEMPORARY_CREDENTIALS.AccessKeyId}}}:${signature}\"; curl -s -H \"Date: ${date}\" -H \"X-AMZ-Security-Token: {{{TEMPORARY_CREDENTIALS.Token}}}\" -H \"Authorization: ${authorization}\" \"https://<myBucketName>.s3.amazonaws.com/<myKey>/f5-appsvcs-templates-1.16.0-1.noarch.rpm\" -o /var/config/rest/downloads/f5-appsvcs-templates-1.16.0-1.noarch.rpm"
extension_packages:
  install_operations:
    - extensionType: fast
      extensionVersion: 1.16.0
      extensionUrl: file:///var/config/rest/downloads/f5-appsvcs-templates-1.16.0-1.noarch.rpm
post_onboard_enabled: []

# azure
controls:
  logLevel: info
  logFilename: /var/log/cloud/bigIpRuntimeInit.log
pre_onboard_enabled: []
runtime_parameters:
  - name: ACCESS_TOKEN
    type: url
    query: access_token
    value: 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fstorage.azure.com%2F'
    headers:
      - name: Metadata
        value: true
      - name: x-ms-version
        value: '2017-11-09'
bigip_ready_enabled:
  - name: download_packages
    type: inline
    commands:
      - 'curl https://<myStorageAccountName>.blob.core.windows.net/<myContainerName>/f5-appsvcs-templates-1.16.0-1.noarch.rpm -o /var/config/rest/downloads/f5-appsvcs-templates-1.16.0-1.noarch.rpm -H "x-ms-version:2017-11-09" -H "Authorization: Bearer {{{ACCESS_TOKEN}}}"'
extension_packages:
  install_operations:
    - extensionType: fast
      extensionVersion: 1.16.0
      extensionUrl: file:///var/config/rest/downloads/f5-appsvcs-templates-1.16.0-1.noarch.rpm
post_onboard_enabled: []

# google
controls:
  logLevel: info
  logFilename: /var/log/cloud/bigIpRuntimeInit.log
pre_onboard_enabled: []
runtime_parameters:
  - name: ACCESS_TOKEN
    type: url
    query: access_token
    value: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/<myServiceAccount>/token'
    headers:
      - name: Metadata-Flavor
        value: Google
bigip_ready_enabled:
  - name: download_packages
    type: inline
    commands:
      - 'curl https://storage.googleapis.com/storage/v1/b/<myBucketName>/f5-appsvcs-templates-1.16.0-1.noarch.rpm -o /var/config/rest/downloads/f5-appsvcs-templates-1.16.0-1.noarch.rpm -H "Authorization: Bearer {{{ACCESS_TOKEN}}}"'
extension_packages:
  install_operations:
    - extensionType: fast
      extensionVersion: 1.16.0
      extensionUrl: file:///var/config/rest/downloads/f5-appsvcs-templates-1.16.0-1.noarch.rpm
post_onboard_enabled: []
amolari commented 2 years ago

@mikeshimkus About the GCP use-case I have the issue that the workaround cannot be directly used for the Vault secretProvider.

My declaration has:
  - name: GLOBAL
    type: secret
    verifyTls: true
    secretProvider:
      type: Vault
      environment: hashicorp
      vaultServer: https://vault.xxxxx.com
      appRolePath: /v1/namespace/auth/approle/login
      secretsEngine: kv2
      secretPath: namespace/secret/data/global
      field: data
      version: '1'
      authBackend:
        type: approle
        roleId:
          type: url
          value: https://storage.googleapis.com/storage/v1/b/<myBucketName>/role-id_file
        secretId:
          type: inline
          value: <secret_id>
          unwrap: true

My buckets are not public => auth is required

The issue are in the roleId:

  1. AFAIK there are no header (Authorization Bearer) I could configure here
  2. if point 1 was possible, I guess I cannot rely on another runtime-parameter (in your example ACCESS_TOKEN) ?

At this time my workaround is to fetch the file with curl (auth bearer) locally and point the url value to file:///. However, I do not like much this approach, as the role-id value is permanent in time (unlike the secret-id) and getting that credential leaked would break the tight security in place.

mikeshimkus commented 2 years ago

Hi @amolari, according to Vault best practices the role ID isn't considered sensitive, however it makes sense to limit its exposure. If it needs to be treated like a secret, you could store it in Google secrets manager, retrieve it using the secrets provider, and reference the returned value using the "inline" type for roleId instead of "url".

Another option would be to use the metadata runtime parameter time to retrieve it from instance metadata, also using the roleId inline type. Or you could bake it into the image as Hashicorp suggests and use the local file URL.

The internal issue we currently have is for securely downloading files to the device, we would need a new issue for extending that bucket authentication to HTTP requests made by other providers. I will chat with the product owners and report back here.

mikeshimkus commented 2 years ago

@amolari This could work as well:

controls:
  logLevel: info
  logFilename: /var/log/cloud/bigIpRuntimeInit.log
runtime_parameters:
  - name: ACCESS_TOKEN
    type: url
    query: access_token
    value: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/<myServiceAccount>/token'
    headers:
      - name: Metadata-Flavor
        value: Google
  - name: ROLE_ID
    type: url
    value: 'https://storage.googleapis.com/storage/v1/b/<myBucketName>/role-id_file'
    headers:
      - name: Authorization
        value: Bearer {{{ACCESS_TOKEN}}}
  - name: GLOBAL
    type: secret
    verifyTls: true
    secretProvider:
      type: Vault
      environment: hashicorp
      vaultServer: https://vault.xxxxx.com
      appRolePath: /v1/namespace/auth/approle/login
      secretsEngine: kv2
      secretPath: namespace/secret/data/global
      field: data
      version: '1'
      authBackend:
        type: approle
        roleId:
          type: inline
          value: {{{ROLE_ID}}}
        secretId:
          type: inline
          value: <secret_id>
          unwrap: true
amolari commented 2 years ago

@mikeshimkus thank you for your answer. I've tested your proposed workaround and I see:

  1. (syntax): value: {{{ROLE_ID}}} gives the error: error: Invalid declaration: "data.runtime_parameters[2].secretProvider.authBackend.roleId.value should be string, data.runtime_parameters[2].secretProvider.authBackend.roleId.value should be string, data.runtime_parameters[2].secretProvider.authBackend.roleId should match \"then\" schema
  2. no invalid declaration when using value: "{{{ROLE_ID}}}". However, it doesn't get the value => silly: Request response: 400 {"errors":["missing role_id"]}

To find out if this "chaining" of runtime_parameters works, I've set a troubleshooting command in the post_onboard_enabled such as

  - name: troubleshoot
    type: inline
    commands:
      - echo "ACCESS_TOKEN= {{{ ACCESS_TOKEN }}}"
      - echo "ROLE_ID= {{{ ROLE_ID }}}"

the outputed values are correct.

My guess is that refering to another runtime_parameter is not working in the specific type=secret use-case. Is that the case? Thank you

mikeshimkus commented 2 years ago

It seems like runtime init only resolves parameters twice, not specifically related to the secret type. In this case it may require three rounds of parameter resolution.

Seems like implementing this as part of a secure downloader would be better, but need to examine that. If part of that were a built-in function to get IAM tokens, I think it would allow you to do this (but would need to be separate from downloader itself, since downloading to the device doesn't work for you).