Skip to content

Commit d42d7c7

Browse files
authored
Merge pull request #248 from crazy-max/buildkit-secrets
build/bake: secrets support
2 parents 3c35e71 + 0136eea commit d42d7c7

7 files changed

Lines changed: 355 additions & 168 deletions

File tree

‎.github/workflows/.test-bake.yml‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,27 @@ jobs:
477477
const builderOutputs = JSON.parse(core.getInput('builder-outputs'));
478478
core.info(JSON.stringify(builderOutputs, null, 2));
479479
480+
bake-secret:
481+
uses: ./.github/workflows/bake.yml
482+
permissions:
483+
contents: read
484+
id-token: write
485+
with:
486+
artifact-upload: false
487+
context: test
488+
output: local
489+
target: foosec
490+
secrets:
491+
build-secrets: |+
492+
fixture.plain: |
493+
alpha-line
494+
beta-line
495+
foosec:
496+
fixture.json: ${{ toJSON(format('gamma-line{0}delta-line{0}', fromJSON('"\n"'))) }}
497+
fixture_fuu: standard-secret
498+
fixture_keep: |+
499+
keep-line
500+
480501
bake-set-runner:
481502
uses: ./.github/workflows/bake.yml
482503
permissions:

‎.github/workflows/.test-build.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,25 @@ jobs:
523523
const builderOutputs = JSON.parse(core.getInput('builder-outputs'));
524524
core.info(JSON.stringify(builderOutputs, null, 2));
525525
526+
build-secret:
527+
uses: ./.github/workflows/build.yml
528+
permissions:
529+
contents: read
530+
id-token: write
531+
with:
532+
artifact-upload: false
533+
file: test/secret.Dockerfile
534+
output: local
535+
secrets:
536+
build-secrets: |+
537+
fixture.plain: |
538+
alpha-line
539+
beta-line
540+
fixture.json: ${{ toJSON(format('gamma-line{0}delta-line{0}', fromJSON('"\n"'))) }}
541+
fixture_fuu: standard-secret
542+
fixture_keep: |+
543+
keep-line
544+
526545
build-set-runner:
527546
uses: ./.github/workflows/build.yml
528547
permissions:

‎.github/workflows/bake.yml‎

Lines changed: 125 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ on:
149149
registry-auths:
150150
description: "Raw authentication to registries, defined as YAML objects (for image output)"
151151
required: false
152+
build-secrets:
153+
description: "YAML object mapping BuildKit secret IDs to values, with optional nested target mappings"
154+
required: false
152155
github-token:
153156
description: "GitHub Token used to authenticate against the repository for Git context"
154157
required: false
@@ -215,6 +218,7 @@ jobs:
215218
metaImages: ${{ steps.set.outputs.metaImages }}
216219
sign: ${{ steps.set.outputs.sign }}
217220
privateRepo: ${{ steps.set.outputs.privateRepo }}
221+
targets: ${{ steps.set.outputs.targets }}
218222
ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }}
219223
proxyNetwork: ${{ steps.set.outputs.proxyNetwork }}
220224
steps:
@@ -400,7 +404,7 @@ jobs:
400404
}
401405
);
402406
await core.group(`Set envs`, async () => {
403-
core.info(JSON.stringify(envs, null, 2));
407+
core.info(JSON.stringify(Object.keys(envs).sort(), null, 2));
404408
});
405409
406410
const metaImages = inpMetaImages.map(image => image.toLowerCase());
@@ -414,9 +418,15 @@ jobs:
414418
try {
415419
await core.group(`Validating definition`, async () => {
416420
const bake = new Bake();
421+
// Resolve the graph without local secret files. The build job validates
422+
// secrets after applying the workflow-provided source overrides.
423+
const validationOverrides = inpSet.filter(override => {
424+
const key = override.split('=', 1)[0].split('.')[1];
425+
return !['secret', 'secrets', 'secrets+'].includes(key);
426+
});
417427
def = await bake.getDefinition({
418428
files: inpFiles,
419-
overrides: inpSet,
429+
overrides: [...validationOverrides, '*.secrets='],
420430
sbom: inpSbom ? `generator=${inpSbomImage}` : 'false',
421431
source: bakeSource,
422432
targets: [inpTarget]
@@ -426,8 +436,9 @@ jobs:
426436
if (!def) {
427437
throw new Error('Bake definition not set');
428438
}
429-
BakeTargets.resolve(def, inpTarget);
439+
const targets = BakeTargets.resolve(def, inpTarget);
430440
target = inpTarget;
441+
core.setOutput('targets', JSON.stringify(targets));
431442
});
432443
} catch (error) {
433444
core.setFailed(error);
@@ -710,6 +721,82 @@ jobs:
710721
cosignPath,
711722
`${containerName}:/usr/bin/cosign`
712723
]);
724+
-
725+
name: Configure AWS credentials
726+
if: ${{ needs.registry-identities.outputs.aws-ecr-enabled == 'true' }}
727+
uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
728+
with:
729+
role-to-assume: ${{ needs.registry-identities.outputs.aws-ecr-role-to-assume }}
730+
aws-region: ${{ needs.registry-identities.outputs.aws-ecr-region }}
731+
-
732+
name: Login to Amazon ECR
733+
if: ${{ needs.registry-identities.outputs.aws-ecr-enabled == 'true' }}
734+
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
735+
env:
736+
AWS_ACCOUNT_IDS: ${{ needs.registry-identities.outputs.aws-ecr-account-ids }}
737+
with:
738+
registry-auth: |
739+
- registry: ${{ needs.registry-identities.outputs.aws-ecr-registry }}
740+
-
741+
name: Authenticate to Google Cloud
742+
id: gcp-wif-auth
743+
if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }}
744+
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
745+
with:
746+
token_format: access_token
747+
workload_identity_provider: ${{ needs.registry-identities.outputs.gcp-wif-workload-identity-provider }}
748+
service_account: ${{ needs.registry-identities.outputs.gcp-wif-service-account }}
749+
project_id: ${{ needs.registry-identities.outputs.gcp-wif-project-id }}
750+
create_credentials_file: false
751+
export_environment_variables: false
752+
-
753+
name: Login to Google Artifact Registry
754+
if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }}
755+
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
756+
with:
757+
registry-auth: |
758+
- registry: ${{ needs.registry-identities.outputs.gcp-wif-registry }}
759+
username: oauth2accesstoken
760+
password: ${{ steps.gcp-wif-auth.outputs.access_token }}
761+
-
762+
name: Login to Docker Hub with OIDC
763+
if: ${{ needs.registry-identities.outputs.dockerhub-oidc-enabled == 'true' }}
764+
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
765+
env:
766+
DOCKERHUB_OIDC_CONNECTIONID: ${{ needs.registry-identities.outputs.dockerhub-oidc-connection-id }}
767+
with:
768+
registry-auth: |
769+
- registry: ${{ needs.registry-identities.outputs.dockerhub-oidc-registry }}
770+
username: ${{ needs.registry-identities.outputs.dockerhub-oidc-username }}
771+
-
772+
name: Authenticate to Azure
773+
if: ${{ needs.registry-identities.outputs.azure-acr-enabled == 'true' }}
774+
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
775+
with:
776+
client-id: ${{ needs.registry-identities.outputs.azure-acr-client-id }}
777+
tenant-id: ${{ needs.registry-identities.outputs.azure-acr-tenant-id }}
778+
subscription-id: ${{ needs.registry-identities.outputs.azure-acr-subscription-id }}
779+
-
780+
name: Login to Azure Container Registry
781+
if: ${{ needs.registry-identities.outputs.azure-acr-enabled == 'true' }}
782+
env:
783+
ACR_REGISTRY: ${{ needs.registry-identities.outputs.azure-acr-registry }}
784+
AZURE_CORE_OUTPUT: none
785+
run: az acr login --name "${ACR_REGISTRY%.azurecr.io}"
786+
-
787+
name: Set up chainctl
788+
if: ${{ needs.registry-identities.outputs.chainguard-enabled == 'true' }}
789+
uses: chainguard-dev/setup-chainctl@2cddd35a2f120d9973e58094dc6878c93cf58c28 # v0.5.1
790+
with:
791+
identity: ${{ needs.registry-identities.outputs.chainguard-identity }}
792+
apk-host: ${{ needs.registry-identities.outputs.chainguard-apk-host }}
793+
libraries-host: ${{ needs.registry-identities.outputs.chainguard-libraries-host }}
794+
-
795+
name: Login to registry
796+
if: ${{ inputs.push && inputs.output == 'image' && env.REGISTRY_AUTHS_PRESENT == 'true' }}
797+
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
798+
with:
799+
registry-auth: ${{ secrets.registry-auths }}
713800
-
714801
name: Prepare
715802
id: prepare
@@ -721,6 +808,8 @@ jobs:
721808
INPUT_CACHE: ${{ inputs.cache }}
722809
INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }}
723810
INPUT_CACHE-MODE: ${{ inputs.cache-mode }}
811+
INPUT_BUILD-SECRETS: ${{ secrets.build-secrets }}
812+
INPUT_TARGETS: ${{ needs.prepare.outputs.targets }}
724813
INPUT_CONTEXT: ${{ inputs.context }}
725814
INPUT_FILES: ${{ inputs.files }}
726815
INPUT_OUTPUT: ${{ inputs.output }}
@@ -737,15 +826,15 @@ jobs:
737826
INPUT_BAKE-FILE-TAGS: ${{ steps.meta.outputs.bake-file-tags }}
738827
INPUT_BAKE-FILE-ANNOTATIONS: ${{ steps.meta.outputs.bake-file-annotations }}
739828
INPUT_BAKE-FILE-LABELS: ${{ steps.meta.outputs.bake-file-labels }}
740-
INPUT_GITHUB-TOKEN: ${{ secrets.github-token || github.token }}
741829
INPUT_BUILDKIT-PROXY-NETWORK: ${{ inputs.buildkit-proxy-network }}
742830
with:
743831
script: |
744832
const os = require('os');
745833
const { Build } = require('@docker/github-builder-runtime/lib/buildx/build');
834+
const { BuildSecrets } = require('@docker/github-builder-runtime/lib/github-builder/build-secrets');
746835
const { GitHub } = require('@docker/github-builder-runtime/lib/github/github');
747836
const { Util } = require('@docker/github-builder-runtime/lib/util');
748-
837+
749838
const inpPlatform = core.getInput('platform');
750839
const platformPairSuffix = inpPlatform ? `-${inpPlatform.replace(/\//g, '-')}` : '';
751840
core.setOutput('platform-pair-suffix', platformPairSuffix);
@@ -756,6 +845,8 @@ jobs:
756845
const inpCache = core.getBooleanInput('cache');
757846
const inpCacheScope = core.getInput('cache-scope');
758847
const inpCacheMode = core.getInput('cache-mode');
848+
const inpBuildSecrets = core.getInput('build-secrets', {trimWhitespace: false});
849+
const inpTargets = core.getInput('targets');
759850
const inpContext = core.getInput('context');
760851
const inpFiles = Util.getInputList('files');
761852
const inpOutput = core.getInput('output');
@@ -764,7 +855,6 @@ jobs:
764855
const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false});
765856
const inpTarget = core.getInput('target');
766857
const inpVars = Util.getInputList('vars');
767-
const inpGitHubToken = core.getInput('github-token');
768858
const inpBuildkitProxyNetwork = core.getBooleanInput('buildkit-proxy-network');
769859
770860
const inpMetaImages = core.getMultilineInput('meta-images');
@@ -781,7 +871,7 @@ jobs:
781871
tags: inpMetaTags
782872
};
783873
const renderTemplate = value => Util.compileHandlebars(value, {noEscape: true}, {meta});
784-
874+
785875
const gitContextAttrs = GitHub.context.ref.startsWith('refs/tags/') ? {checksum: GitHub.context.sha} : {'fetch-by-commit': 'true'};
786876
const bakeSource = await new Build().gitContext({subdir: inpContext, attrs: gitContextAttrs});
787877
await core.group(`Set source output`, async () => {
@@ -799,7 +889,16 @@ jobs:
799889
core.info(sbom);
800890
core.setOutput('sbom', sbom);
801891
});
802-
892+
893+
let buildSecrets;
894+
try {
895+
buildSecrets = BuildSecrets.prepareBake(inpBuildSecrets, inpTarget, JSON.parse(inpTargets || '[]'));
896+
} catch (err) {
897+
core.setFailed(err.message);
898+
return;
899+
}
900+
core.setOutput('secret-dir', buildSecrets.directory);
901+
803902
const envs = Object.assign({},
804903
inpVars ? inpVars.reduce((acc, curr) => {
805904
const idx = curr.indexOf('=');
@@ -809,12 +908,15 @@ jobs:
809908
return acc;
810909
}, {}) : {},
811910
{
812-
BUILDKIT_MULTI_PLATFORM: '1',
813-
BUILDX_BAKE_GIT_AUTH_TOKEN: inpGitHubToken
911+
BUILDKIT_MULTI_PLATFORM: '1'
814912
}
815913
);
914+
915+
// Git authentication is supplied directly to the Bake action.
916+
delete envs.BUILDX_BAKE_GIT_AUTH_TOKEN;
917+
816918
await core.group(`Set envs`, async () => {
817-
core.info(JSON.stringify(envs, null, 2));
919+
core.info(JSON.stringify(Object.keys(envs).sort(), null, 2));
818920
core.setOutput('envs', JSON.stringify(envs));
819921
});
820922
@@ -878,85 +980,10 @@ jobs:
878980
bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || inpTarget}${platformPairSuffix}${proxyNetworkSuffix}`);
879981
bakeOverrides.push(`*.cache-to=type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget}${platformPairSuffix}${proxyNetworkSuffix},mode=${inpCacheMode}`);
880982
}
983+
bakeOverrides.push(...buildSecrets.inputs);
881984
core.info(JSON.stringify(bakeOverrides, null, 2));
882985
core.setOutput('overrides', bakeOverrides.join(os.EOL));
883986
});
884-
-
885-
name: Configure AWS credentials
886-
if: ${{ needs.registry-identities.outputs.aws-ecr-enabled == 'true' }}
887-
uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
888-
with:
889-
role-to-assume: ${{ needs.registry-identities.outputs.aws-ecr-role-to-assume }}
890-
aws-region: ${{ needs.registry-identities.outputs.aws-ecr-region }}
891-
-
892-
name: Login to Amazon ECR
893-
if: ${{ needs.registry-identities.outputs.aws-ecr-enabled == 'true' }}
894-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
895-
env:
896-
AWS_ACCOUNT_IDS: ${{ needs.registry-identities.outputs.aws-ecr-account-ids }}
897-
with:
898-
registry-auth: |
899-
- registry: ${{ needs.registry-identities.outputs.aws-ecr-registry }}
900-
-
901-
name: Authenticate to Google Cloud
902-
id: gcp-wif-auth
903-
if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }}
904-
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
905-
with:
906-
token_format: access_token
907-
workload_identity_provider: ${{ needs.registry-identities.outputs.gcp-wif-workload-identity-provider }}
908-
service_account: ${{ needs.registry-identities.outputs.gcp-wif-service-account }}
909-
project_id: ${{ needs.registry-identities.outputs.gcp-wif-project-id }}
910-
create_credentials_file: false
911-
export_environment_variables: false
912-
-
913-
name: Login to Google Artifact Registry
914-
if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }}
915-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
916-
with:
917-
registry-auth: |
918-
- registry: ${{ needs.registry-identities.outputs.gcp-wif-registry }}
919-
username: oauth2accesstoken
920-
password: ${{ steps.gcp-wif-auth.outputs.access_token }}
921-
-
922-
name: Login to Docker Hub with OIDC
923-
if: ${{ needs.registry-identities.outputs.dockerhub-oidc-enabled == 'true' }}
924-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
925-
env:
926-
DOCKERHUB_OIDC_CONNECTIONID: ${{ needs.registry-identities.outputs.dockerhub-oidc-connection-id }}
927-
with:
928-
registry-auth: |
929-
- registry: ${{ needs.registry-identities.outputs.dockerhub-oidc-registry }}
930-
username: ${{ needs.registry-identities.outputs.dockerhub-oidc-username }}
931-
-
932-
name: Authenticate to Azure
933-
if: ${{ needs.registry-identities.outputs.azure-acr-enabled == 'true' }}
934-
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
935-
with:
936-
client-id: ${{ needs.registry-identities.outputs.azure-acr-client-id }}
937-
tenant-id: ${{ needs.registry-identities.outputs.azure-acr-tenant-id }}
938-
subscription-id: ${{ needs.registry-identities.outputs.azure-acr-subscription-id }}
939-
-
940-
name: Login to Azure Container Registry
941-
if: ${{ needs.registry-identities.outputs.azure-acr-enabled == 'true' }}
942-
env:
943-
ACR_REGISTRY: ${{ needs.registry-identities.outputs.azure-acr-registry }}
944-
AZURE_CORE_OUTPUT: none
945-
run: az acr login --name "${ACR_REGISTRY%.azurecr.io}"
946-
-
947-
name: Set up chainctl
948-
if: ${{ needs.registry-identities.outputs.chainguard-enabled == 'true' }}
949-
uses: chainguard-dev/setup-chainctl@2cddd35a2f120d9973e58094dc6878c93cf58c28 # v0.5.1
950-
with:
951-
identity: ${{ needs.registry-identities.outputs.chainguard-identity }}
952-
apk-host: ${{ needs.registry-identities.outputs.chainguard-apk-host }}
953-
libraries-host: ${{ needs.registry-identities.outputs.chainguard-libraries-host }}
954-
-
955-
name: Login to registry
956-
if: ${{ inputs.push && inputs.output == 'image' && env.REGISTRY_AUTHS_PRESENT == 'true' }}
957-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
958-
with:
959-
registry-auth: ${{ secrets.registry-auths }}
960987
-
961988
name: Build
962989
id: bake
@@ -967,7 +994,18 @@ jobs:
967994
targets: ${{ steps.prepare.outputs.target }}
968995
sbom: ${{ steps.prepare.outputs.sbom }}
969996
set: ${{ steps.prepare.outputs.overrides }}
997+
github-token: ${{ secrets.github-token || github.token }}
970998
env: ${{ fromJson(steps.prepare.outputs.envs || '{}') }}
999+
-
1000+
name: Remove build secrets
1001+
if: ${{ always() && steps.prepare.outputs.secret-dir != '' }}
1002+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
1003+
env:
1004+
INPUT_SECRET-DIR: ${{ steps.prepare.outputs.secret-dir }}
1005+
with:
1006+
script: |
1007+
const { BuildSecrets } = require('@docker/github-builder-runtime/lib/github-builder/build-secrets');
1008+
BuildSecrets.cleanup(core.getInput('secret-dir', {required: true}));
9711009
-
9721010
name: Get image digest
9731011
id: get-image-digest

0 commit comments

Comments
 (0)