#How do a use WithNewFile to export content that's a secret without it being logged?

1 messages ยท Page 1 of 1 (latest)

small cairn
#

Using the SDK natively, ie usage of container mounting a file, how can I take the output from some code, make sure it's a dagger.Secret, then use the dagger secret content as the filecount to dagger.Directory.WithNewFile? All examples I've found are focused on secret input, but I want to treat the contents of something I'm generating as a secret so it's not not persisted, logged, and protected.

What copilot resorted to ๐Ÿ™‚

// Use container to create file without logging sensitive content
fileContainer := dag.Container().
    From("alpine:latest").
    WithSecretVariable("YAML_CONTENT", dag.SetSecret("some-yaml", string(b))).
    WithExec([]string{"mkdir", "-p", "/output"}).
    WithExec([]string{"sh", "-c", "echo \"$YAML_CONTENT\" > /output/some-yaml.values.yaml"})

dir := fileContainer.Directory("/output")
f := dir.File("some-yaml.values.yaml")
return f, nil

What I'm trying to do in general.

secretContent := dagger.SetSecret("secretcontent", secretsdkvault.RetrieveSecret("foo"))
dir := dag.Directory().WithNewFile("some-yaml.values.yaml", secretContent)

Not yet clear on proper way to handle this so I ensure safe logging, but still output some secret generated content as a file.

small cairn
#

A follow-up to this is a question about variable with a secret.

  • If I create a map, place secrets in it, and then before leaving the function assign this to a dagger secret, without having put this into any dagger container, file, or other, then this would also just be in memory and not traced correct?

For example if I need to retrieve some values from a vault, initialize a struct, marshal it, and then have this in a variable that I then assign the contents to a dagger.Secret, then none of this would be logged as not related to dagger instrumentation right? The auto isntrumentation is specific to the dagger features, and as long as I don't fmt.Println, I'm good with normal Go code not being part of any tracing, correct?

#

<@&946480760016207902> sorry to bump this but it's important to work I have to finish today. If anyone can clarify the 1) in memory is fine and won't be traced until using dagger file creation/methods that get traced 2) how can I safely output a sensitive generated file (all examples show inputs and I'm doing a generation inside dagger)

Appreciate the help! Will check back in an hour or so if anyone can give a boost ๐ŸŒฎ

sterile vapor
#

re 2: as mentioned in the above issue, there's currently no safe way to create a secret out of a command execution within a pipeline

oblique hedge
#

Does upcoming cache control feature open new options for this @sleek turtle ? Like disabling caching for functions that return a secret? Mmm but I guess we would also need a way for the caller to disable caching of the core functions that touch the secret data

small cairn
#

If I mount a dagger directory with sensitive content in it then

  1. it's not logged right?
  2. it is cached though (in ephemeral ci that probably wouldn't matter)

So a workaround could be export directory instead of a secret file?

sleek turtle
oblique hedge
sleek turtle
#

Oh okay, yeah we only handle something as a secret once it has been wrapped up as a Secret

sleek turtle
oblique hedge
#

@sleek turtle as a follow-up to your "callee controls nocache & ttl" PR, does it become feasible to add "caller controls nocache & ttl" for core functions, so you could eg. call withExec(dagger.WithExecOpts{NoCache: true}) to safely prevent your plaintext-spitting command to leak plaintext into the layers?

#

side effect - good-bye cache buster tricks ๐Ÿคฉ

dim prawn
sleek turtle
# oblique hedge <@949034677610643507> as a follow-up to your "callee controls nocache & ttl" PR,...

That's possible yes. Relatedly I also think we really need to add support for a secret provider which is backed by a module function call (e.g. a function that returns *Secret and can then be used as a secret provider). Then we can deprecate SetSecret in favor of that. Function secret providers (and anything they call) would then inherently get the "do not ever cache anything on disk persistently" behavior

sleek turtle
oblique hedge
sleek turtle
# oblique hedge Yeah that would be cool. Doable?

Yeah, mostly plumbing. Honestly the SDK DX would be the hardest part, but I think w/ self calls it could be something like secret := dag.FunctionSecret(dag.Self().MySecretFunction()) (approximate syntax)

oblique hedge
#

We could dodge SDK work and make it dag.Secret("self://my-secret-function") ๐Ÿ˜› (or function://my-secret-function). Could also support dependencies: function://mydep/secret-function)

sleek turtle
sterile vapor
oblique hedge
#

Makes sense two split the problem in 2 parts IMO

#

Since we already have string-typed for every other provider

small cairn
sterile vapor
small cairn
#

Regardless definitely appreciate the quick support to confirm behavior daggerfire

sterile vapor
#

can't you use some Go code to retrieve your secret within your function instead of retrieving it from a container exec?

small cairn
#

I'm generating a secret manifest for kubenetes with secrets.

Later I then need to pass it in and run encryption on it with SOPS

#

So the secret content has to come out of the dagger function and then later I pass it back in to do encryption.

sterile vapor
small cairn
#

azure SDK retrieves from keyvault to create a k8s manifest

sterile vapor
small cairn
#
  1. ci step: (scoped service connection just for this) -> fetch secrets to generate a secrets file
  2. ci step: (scoped to new service connection for encrypt key) -> pass in the secrets file and use this to encrypt with sops and output a new encrypted file.

I can't combine both into a single step because each has it's own connection requirements. So I need to generate an artifact, then pass into the next stage of the CI job.

#

here's a pseudocode example of this so you can get context

#
jobs:
  - job: secrets_and_encryption
    steps:
      # Step 1: Fetch secrets with vault service connection
      - task: AzureCLI@2
        displayName: fetch-secrets
        inputs:
          azureSubscription: vault-service-connection
          scriptType: bash
          inlineScript: |
            # Login and setup Azure context
            az login --service-principal -u $servicePrincipalId -p $servicePrincipalKey --tenant $tenantId
            
            # Fetch secrets from Key Vault using Dagger
            dagger call -m 'https://token@org.com/project/_git/repo@branch' \
              azure-keyvault-to-values-file-sdk \
              --vaults "vault1,vault2" \
              --namespace "my-app" \
              --role "my-role" \
              --azure-tenant-id env://AZURE_TENANT_ID \
              --azure-subscription-id env://AZURE_SUBSCRIPTION_ID \
              --arm-token=cmd://"az account get-access-token --scope https://vault.azure.net/.default --query accessToken -o tsv" \
              export --path="./.artifacts/secrets-raw/my-app/env/role"
#

and next step

      # Step 2: Encrypt secrets with SOPS using encryption service connection  
      - task: AzureCLI@2
        displayName: encrypt-secrets
        inputs:
          azureSubscription: encryption-service-connection
          scriptType: bash
          inlineScript: |
            # Login with encryption service principal
            az login --service-principal -u $servicePrincipalId -p $servicePrincipalKey --tenant $tenantId
            
            # Encrypt secrets using SOPS with Dagger
            dagger call -m 'https://token@org.com/project/_git/repo@branch' \
              sops-encrypt \
              --secrets-dir ".artifacts/secrets-raw/my-app/env/role/" \
              --mode "ci" \
              --vault-urls "https://keyvault1.vault.azure.net,https://keyvault2.vault.azure.net" \
              --azure-client-id env://AZURE_CLIENT_ID \
              --azure-client-secret env://AZURE_CLIENT_SECRET \
              --azure-tenant-id env://AZURE_TENANT_ID \
              export --path="./.artifacts/encrypted/my-app/env/role"

      # Step 3: Publish encrypted artifacts
      - task: PublishPipelineArtifact@1
        displayName: publish-encrypted-secrets
        inputs:
          targetPath: .artifacts/encrypted
          artifact: encrypted-secrets
          publishLocation: pipeline
sterile vapor
#

The scenario seems a bit complex in the way that you have this multiple connection restriction which forces you to pass the raw secret from one job to the other via artifacts which is not a secure medium in the first place. Gtihub specially advises against storing sensitive information in artifacts

small cairn
#

i'm using the word artifact generally.
It's step, temporary working directory, gitignored.
I produce the output store in that, and in next task (not a job level thing), i run the transformation.

There is no publishing to any artifact feed, "artifact" type that gets redownloaded in another step, it's all just working locally.

sterile vapor
small cairn
#

yeah, right now this is what I had to do and I'm not certain if it actually keeps the secret truly secret ๐Ÿ™‚
WithNewFile logs the entire text, this does, but is it cached, and more importantly would any failure result in that showing up inthe logs?

secretFile := dag.SetSecret("secrets-yaml", finalYAML)
secureContainer := dag.Container().From("alpine:latest").
WithSecretVariable("SECRETS_YAML_CONTENT", secretFile).
WithExec([]string{"mkdir", "-p", "/output"}).
WithExec([]string{"sh", "-c", "printf '%s' \"$SECRETS_YAML_CONTENT\" > /output/secrets.yaml"})
return secureContainer.Directory("/output").File("secrets.yaml"), nil
#

what i understand:

  • in memory go objects no problem.
  • once I assign to something like dagger file it's cached technically though wouldn't be logged with .File, only withnewfile? Not sure if that's something I can rely on to stay the same long term. The secret in that container is protected from output, but not confident the final line is

If you want to do a huddle in the next hour to chat if easier just let me know and I'll make time if I'm not being clear enough

#

and if I can get away with something like this too, maybe that would work?

func (m *Chart) ExportDirectoryToHost(ctx context.Context, d *dagger.Directory, hostPath string) (string, error) {
    // Use Directory.Export to write directory to host filesystem
    return d.Export(ctx, hostPath)
}
oblique hedge
small cairn
#

facepalm gotcha, thus why I still had to run export --path as didn't make any difference.

oblique hedge
#

Reminder you can also dagger call -o ./dest ๐Ÿ™‚ (it calls export under the hood)

small cairn
#

right someone tool me that, i just don't like using shorthand flags in anything other than local work. It's just a general practice of being anti alias/shortcut for readability.
I might just flip someday though ;-p

#

so the gist from what I gather is even with .File() I'm gonna still be caching and having the contents potentially show up in the CI logs if a problem occurs, so there is no secure way for me to drop out a file from my function without the risk of it being visible?

Just to make sure I'm on the same page before I try to reapproach this

oblique hedge
oblique hedge
small cairn
#

Maybe I'm misunderstanding... all the patterns I'd seen in past where

dagger -m '' call functionname --flags foo export --path .` 

is using the -o now the preferred way?

oblique hedge
small cairn
#

๐Ÿ˜† this is what I'm running in the CI and wrap in task. Maybe that's the confusion. It doesn't save me a lot right now

SCOPE="https://vault.azure.net/.default"
dagger call azure-keyvault-to-values-file-sdk \
  --vaults="${VAULTS:-vault1,vault2,vault3}" \
  --namespace="${NAMESPACE:-default}" \
  --role="${ROLE:-app}" \
  --azure-tenant-id=env://AZURE_TENANT_ID \
  --azure-subscription-id=env://AZURE_SUBSCRIPTION_ID \
  --arm-token=cmd://"az account get-access-token --scope $SCOPE --query accessToken -o tsv" \
  --arm-expires-on=cmd://"az account get-access-token --scope $SCOPE --query expires_on -o tsv" \
  export --path=.artifacts/out/${NAMESPACE:-default}/${ENV:-dev}/${ROLE:-app}/secrets-sdk.yaml

BUT the concept of the env file stuff you have as an open issue does maybe ๐Ÿ™‚

Right now I just use task dagger:secrets-generate for example and I'm fine, but I'm ok with the verbosity if I gain cross platform reliable automation.

#

The secrets thing is a big problem for me right now though so I'll have to revisit, since I didn't understand this up front and might mean I can't use dagger fully like I'd hoped ๐Ÿ˜ญ

I know this isn't an ideal usage anyway, but it's what I have to work with in the time here since changing entire approach is a later game . appreciate the heads up and clarity.

#

thankfully I stuck with Go SDK for azure and such mostly so the main benefit I can see is likely can flip to just a go cli for now for the basic actions and revisit doing natively in dagger in the future so while some big time was lost, it was a good learning experience that I can use to do more complex modules in future.

So far have 1 for changelog generation, 1 for generating repo inventory for a matrix job with renovate, and then this. Getting further along ๐Ÿ’ช