Open11

oras

Shunsuke SuzukiShunsuke Suzuki

ORAS CLI

https://github.com/suzuki-shunsuke/monitor-gh-follower-action/blob/82758c108e8f5fb16180ba31a235c488d0a4a176/action.yaml#L33-L69

https://oras.land/docs/how_to_guides/authentication
https://oras.land/docs/commands/oras_login

echo "$GITHUB_TOKEN" | oras login -u octokit --password-stdin ghcr.io

https://oras.land/docs/how_to_guides/pushing_and_pulling
https://oras.land/docs/commands/oras_pull

oras pull "ghcr.io/$GITHUB_REPOSITORY/followers.json:latest"

https://oras.land/docs/commands/oras_push

oras push "ghcr.io/$GITHUB_REPOSITORY/followers.json:latest" followers.json
  • GitHub access token
  • registry (ghcr.io)
  • path
    • repository
    • path
    • tag
  • pushed file path
Shunsuke SuzukiShunsuke Suzuki

セキュアな認証

CI で使う前提で書く。

  • oras login はどこかに credential を永続化しているはずだが、これはセキュリティ上望ましくない。 CI では後続の任意の step が credential にアクセス可能
  • まず oras login はどこに永続化しているのか
  • oras login には --identity-token, --identity-token-stdin があるのでこれを使えば -u octokit は不要かもしれない
  • oras pull, push には --identity-token, --identity-token-stdin があるのでこれを使えば credential を永続化しないで済むのでは
    • oras pull, push が本当に永続化してないかは要確認
Shunsuke SuzukiShunsuke Suzuki

デフォルトでは docker config file path

https://github.com/oras-project/oras/blob/6511fe3dc5d0465450475799b0a713c05544b692/internal/credential/store.go#L22-L28

  1. $DOCKER_CONFIG/config.json
  2. $HOME/.docker/config.json
// getDockerConfigPath returns the path to the default docker config file.
func getDockerConfigPath() (string, error) {
	// first try the environment variable
	configDir := os.Getenv(dockerConfigDirEnv)
	if configDir == "" {
		// then try home directory
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("failed to get user home directory: %w", err)
		}
		configDir = filepath.Join(homeDir, dockerConfigFileDir)
	}
	return filepath.Join(configDir, dockerConfigFileName), nil
}
Shunsuke SuzukiShunsuke Suzuki

GitHub App User Access Token では失敗。

$ ghtkn get | oras login --identity-token-stdin ghcr.io
Error response from registry: unsupported: The operation is unsupported.

read:packages 権限のある classic PAT でも失敗

$ echo $GHTOKEN | oras login --identity-token-stdin ghcr.io
Error response from registry: unsupported: The operation is unsupported.

--password-stdin では成功したので、 GitHub Access Token は ghcr.io の identity-token としては使えないっぽい。

$ echo $GHTOKEN | oras login -u octokit --password-stdin ghcr.io
Login Succeeded
Shunsuke SuzukiShunsuke Suzuki

oras login を実行すると ~/.docker/config.json に <login name>:<github access token> を base64 encode した値が書き込まれていた。

{
	"auths": {
		"ghcr.io": {
			"auth": "xxx"
		}
	}
}
Shunsuke SuzukiShunsuke Suzuki

その状態であれば当然 pull は成功する。

$ oras pull "ghcr.io/suzuki-shunsuke/monitor-gh-follower-action/followers.json:latest"
✓ Pulled      followers.json                                                                                        60.5/60.5 KB 100.00%   22ms
  └─ sha256:9609aff924952a473b58716e6a206e401efedf3d65a407170d5f5cb793a2fab4                                                                   
✓ Pulled      application/vnd.oci.image.manifest.v1+json                                                              596/596  B 100.00%  909µs
  └─ sha256:4b2a0f97825931bc5ce025772721c5e804f9bfb1fb07097d5d65d9de58705492                                                                   
Pulled [registry] ghcr.io/suzuki-shunsuke/monitor-gh-follower-action/followers.json:latest
Digest: sha256:4b2a0f97825931bc5ce025772721c5e804f9bfb1fb07097d5d65d9de58705492

認証情報を消すと失敗する。

{
  "auths": {}
}
$ oras pull "ghcr.io/suzuki-shunsuke/monitor-gh-follower-action/followers.json:latest"
Error response from registry: unauthorized: authentication required

oras pull に option で認証情報を渡すと成功する。

$ echo "$GHTOKEN" | oras pull -u octokit --password-stdin "ghcr.io/suzuki-shunsuke/monitor-gh-follower-action/followers.json:latest"
✓ Pulled      followers.json                                                                                        60.5/60.5 KB 100.00%   45ms
  └─ sha256:9609aff924952a473b58716e6a206e401efedf3d65a407170d5f5cb793a2fab4                                                                   
✓ Pulled      application/vnd.oci.image.manifest.v1+json                                                              596/596  B 100.00%  140µs
  └─ sha256:4b2a0f97825931bc5ce025772721c5e804f9bfb1fb07097d5d65d9de58705492                                                                   
Pulled [registry] ghcr.io/suzuki-shunsuke/monitor-gh-follower-action/followers.json:latest
Digest: sha256:4b2a0f97825931bc5ce025772721c5e804f9bfb1fb07097d5d65d9de58705492

こうすると ~/.docker/config.json に credential は書き込まれないので安全

Shunsuke SuzukiShunsuke Suzuki

まとめ

CI で oras を使う場合

  • oras login は ~/.docker/config.json に credential が永続化されるので使わない
  • oras pull, push に -u octokit --password-stdin を渡して認証すれば credential は永続化されない
    • -u の値は octokit のようなダミーでも良い
  • GitHub Access Token は --identity-token には対応していないので --password-stdin を使う
echo "$GITHUB_TOKEN" |
  oras pull -u octokit --password-stdin \
    "ghcr.io/suzuki-shunsuke/monitor-gh-follower-action/followers.json:latest"
Shunsuke SuzukiShunsuke Suzuki

container registry のセキュリティ

oras を使って container registry にファイルを保存するのは便利ではあるが、
container registry はセキュリティ的に注意が必要。
書き込み権限のあるユーザーの PAT を使えば上書きできる
branch rulesets のような保護機能はない。

cosign のようなツールで署名・検証すれば改竄を検知できそう。
ただ、 oras でそれが可能かは要確認

Shunsuke SuzukiShunsuke Suzuki

oras に署名・検証の機能はないが、 cosign で署名して一緒に push し、両方 pull して cosign で検証すればよいのではないか?

署名

https://github.com/suzuki-shunsuke/go-release-workflow/blob/f89428865e2e1e3069307b38ecf0f10c48e4f27d/.github/workflows/release.yaml#L146-L149

cosign sign-blob "$checksum" \
  -y \
  --bundle "${checksum}.bundle" \
  --oidc-provider github

検証

https://docs.sigstore.dev/cosign/verifying/verify/

https://github.com/suzuki-shunsuke/ci-info/blob/main/INSTALL.md#3-cosign

cosign verify-blob \
  "$checksum_file" \
  --bundle "${checksum_file}.bundle" \
  --certificate-identity-regexp 'https://github\.com/suzuki-shunsuke/go-release-workflow/\.github/workflows/release\.yaml@.*' \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
cosign verify-blob --help
cosign verify-blob --help
Verify a signature on the supplied blob input using the specified key reference.
You may specify either a key, a certificate or a kms reference to verify against.
	If you use a key or a certificate, you must specify the path to them on disk.

The signature may be specified as a path to a file or a base64 encoded string.
The blob may be specified as a path to a file or - for stdin.

Usage:
cosign verify-blob [flags]

Examples:
 cosign verify-blob (--key <key path>|<key url>|<kms uri>)|(--certificate <cert>) --signature <sig> <blob>

  # Verify a simple blob and message
  cosign verify-blob --key cosign.pub (--signature <sig path>|<sig url> msg)

  # Verify a signature with certificate and CA certificate chain
  cosign verify-blob --certificate cert.pem --certificate-chain certchain.pem --signature $sig <blob>

  # Verify a signature with CA roots and optional intermediate certificates
  cosign verify-blob --certificate cert.pem --ca-roots caroots.pem [--ca-intermediates caintermediates.pem] --signature $sig <blob>

  # Verify a signature from an environment variable
  cosign verify-blob --key cosign.pub --signature $sig msg

  # verify a signature with public key provided by URL
  cosign verify-blob --key https://host.for/<FILE> --signature $sig msg

  # verify a signature with signature and key provided by URL
  cosign verify-blob --key https://host.for/<FILE> --signature https://example.com/<SIG>

  # Verify a signature against Azure Key Vault
  cosign verify-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] --signature $sig <blob>

  # Verify a signature against AWS KMS
  cosign verify-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] --signature $sig <blob>

  # Verify a signature against Google Cloud KMS
  cosign verify-blob --key gcpkms://projects/[PROJECT ID]/locations/[LOCATION]/keyRings/[KEYRING]/cryptoKeys/[KEY] --signature $sig <blob>

  # Verify a signature against Hashicorp Vault
  cosign verify-blob --key hashivault://[KEY] --signature $sig <blob>

  # Verify a signature against GitLab with project name
  cosign verify-blob --key gitlab://[OWNER]/[PROJECT_NAME]  --signature $sig <blob>

  # Verify a signature against GitLab with project id
  cosign verify-blob --key gitlab://[PROJECT_ID]  --signature $sig <blob>

  # Verify a signature against a certificate
  cosign verify-blob --certificate <cert> --signature $sig <blob>


Flags:
    --bundle='':
	path to bundle FILE

    --ca-intermediates='':
	path to a file of intermediate CA certificates in PEM format which will be needed when building the
	certificate chains for the signing certificate. The flag is optional and must be used together with
	--ca-roots, conflicts with --certificate-chain.

    --ca-roots='':
	path to a bundle file of CA certificates in PEM format which will be needed when building the certificate
	chains for the signing certificate. Conflicts with --certificate-chain.

    --certificate='':
	path to the public certificate. The certificate will be verified against the Fulcio roots if the
	--certificate-chain option is not passed.

    --certificate-chain='':
	path to a list of CA certificates in PEM format which will be needed when building the certificate chain for
	the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and
	end with the root certificate. Conflicts with --ca-roots and --ca-intermediates.

    --certificate-github-workflow-name='':
	contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed
	workflow.

    --certificate-github-workflow-ref='':
	contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was
	based upon.

    --certificate-github-workflow-repository='':
	contains the repository claim from the GitHub OIDC Identity token that contains the repository that the
	workflow run was based upon

    --certificate-github-workflow-sha='':
	contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run
	was based upon.

    --certificate-github-workflow-trigger='':
	contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that
	triggered the workflow run

    --certificate-identity='':
	The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP
	addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless
	flows.

    --certificate-identity-regexp='':
	A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described
	at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set
	for keyless flows.

    --certificate-oidc-issuer='':
	The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or
	https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be
	set for keyless flows.

    --certificate-oidc-issuer-regexp='':
	A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax
	described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or
	--certificate-oidc-issuer-regexp must be set for keyless flows.

    --experimental-oci11=false:
	set to true to enable experimental OCI 1.1 behaviour (unrelated to bundle format)

    -h, --help=false:
	help for verify-blob

    --insecure-ignore-sct=false:
	when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a
	certificate transparency log

    --insecure-ignore-tlog=false:
	ignore transparency log verification, to be used when an artifact signature has not been uploaded to the
	transparency log. Artifacts cannot be publicly verified when not included in a log

    --key='':
	path to the public key file, KMS URI or Kubernetes Secret

    --max-workers=10:
	the amount of maximum workers for parallel executions

    --new-bundle-format=true:
	expect the signature/attestation to be packaged in a Sigstore bundle

    --offline=false:
	only allow offline verification

    --private-infrastructure=false:
	skip transparency log verification when verifying artifacts in a privately deployed infrastructure

    --rekor-url='https://rekor.sigstore.dev':
	address of rekor STL server

    --rfc3161-timestamp='':
	path to RFC3161 timestamp FILE

    --sct='':
	path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a
	certificate contains an SCT, verification will check both the detached and embedded SCTs.

    --signature='':
	signature content or path or remote URL

    --signature-digest-algorithm='sha256':
	digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512)

    --sk=false:
	whether to use a hardware security key

    --slot='':
	security key slot to use for generated key (default: signature)
	(authentication|signature|card-authentication|key-management)

    --timestamp-certificate-chain='':
	path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA
	certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if
	not present in the timestamp

    --trusted-root='':
	Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.

    --use-signed-timestamps=false:
	verify rfc3161 timestamps

Global Flags:
      --output-file string   log output to a file
  -t, --timeout duration     timeout for commands (default 3m0s)
  -d, --verbose              log debug output