Skip to content
Merged
1 change: 1 addition & 0 deletions cmd/notation/blob/policy/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func Cmd() *cobra.Command {
command.AddCommand(
importCmd(),
showCmd(),
initCmd(),
)

return command
Expand Down
115 changes: 115 additions & 0 deletions cmd/notation/blob/policy/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright The Notary Project Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package policy

import (
"encoding/json"
"fmt"
"os"

"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/notaryproject/notation/cmd/notation/internal/display"
"github.com/notaryproject/notation/cmd/notation/internal/display/output"
"github.com/notaryproject/notation/internal/osutil"
"github.com/spf13/cobra"
)

type initOpts struct {
printer *output.Printer
name string
trustStores []string
trustedIdentities []string
force bool
global bool
}

func initCmd() *cobra.Command {
opts := initOpts{}
command := &cobra.Command{
Use: `init [flags] --name <policy_name> --trust-store "<store_type>:<store_name>" --trusted-identity "<trusted_identity>"`,
Short: "Initialize blob trust policy configuration",
Long: `Initialize blob trust policy configuration.

Example - init a blob trust policy configuration with a trust store and a trusted identity:
notation blob policy init --name examplePolicy --trust-store ca:exampleStore --trusted-identity "x509.subject: C=US, ST=WA, O=acme-rockets.io"
`,
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
opts.printer = output.NewPrinter(cmd.OutOrStdout(), cmd.OutOrStderr())
},
RunE: func(cmd *cobra.Command, args []string) error {
return runInit(&opts)
},
}

command.Flags().StringVarP(&opts.name, "name", "n", "", "name of the blob trust policy")
command.Flags().StringArrayVar(&opts.trustStores, "trust-store", nil, "trust store in the format \"<store_type>:<store_name>\"")
command.Flags().StringArrayVar(&opts.trustedIdentities, "trusted-identity", nil, "trusted identity, use the format \"x509.subject:<subject_of_signing_certificate>\" for x509 CA scheme and \"<signing_authority_identity>\" for x509 signingAuthority scheme")
command.Flags().BoolVar(&opts.force, "force", false, "override the existing blob trust policy configuration, never prompt (default --force=false)")
command.Flags().BoolVar(&opts.global, "global", false, "set the policy as the global policy (default --global=false)")
command.MarkFlagRequired("name")
command.MarkFlagRequired("trust-store")
command.MarkFlagRequired("trusted-identity")
return command
}

func runInit(opts *initOpts) error {
blobPolicy := trustpolicy.BlobDocument{
Version: "1.0",
TrustPolicies: []trustpolicy.BlobTrustPolicy{
{
Name: opts.name,
SignatureVerification: trustpolicy.SignatureVerification{
VerificationLevel: trustpolicy.LevelStrict.Name,
},
TrustStores: opts.trustStores,
TrustedIdentities: opts.trustedIdentities,
GlobalPolicy: opts.global,
},
},
}
if err := blobPolicy.Validate(); err != nil {
return fmt.Errorf("invalid blob policy: %w", err)
}

// optional confirmation
if _, err := trustpolicy.LoadBlobDocument(); err == nil {
if !opts.force {
confirmed, err := display.AskForConfirmation(os.Stdin, "The blob trust policy configuration already exists, do you want to overwrite it?", opts.force)
if err != nil {
return err
}

Check warning on line 93 in cmd/notation/blob/policy/init.go

View check run for this annotation

Codecov / codecov/patch

cmd/notation/blob/policy/init.go#L92-L93

Added lines #L92 - L93 were not covered by tests
if !confirmed {
return nil
}
} else {
opts.printer.PrintErrorf("Warning: existing blob trust policy configuration will be overwritten\n")
}
}

policyPath, err := dir.ConfigFS().SysPath(dir.PathBlobTrustPolicy)
if err != nil {
return fmt.Errorf("failed to obtain path of blob trust policy configuration: %w", err)
}

Check warning on line 105 in cmd/notation/blob/policy/init.go

View check run for this annotation

Codecov / codecov/patch

cmd/notation/blob/policy/init.go#L104-L105

Added lines #L104 - L105 were not covered by tests
policyJSON, err := json.MarshalIndent(blobPolicy, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal blob trust policy: %w", err)
}

Check warning on line 109 in cmd/notation/blob/policy/init.go

View check run for this annotation

Codecov / codecov/patch

cmd/notation/blob/policy/init.go#L108-L109

Added lines #L108 - L109 were not covered by tests
if err = osutil.WriteFile(policyPath, policyJSON); err != nil {
return fmt.Errorf("failed to write blob trust policy configuration: %w", err)
}

return opts.printer.Printf("Successfully initialized blob trust policy file to %s.\n", policyPath)
}
12 changes: 6 additions & 6 deletions specs/proposals/blob-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ For file-based distribution, such as SBOMs or release artifacts shared via a web
- Set up trust policy for blobs with a new command `notation blob policy init`. This command streamlines the process, eliminating the need for users to consult documentation for the correct trust policy format and preventing the accidental use of policies intended for OCI artifact verification.

```shell
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trust-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trusted-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
```

Show the policies configured for verifying blobs:
Expand Down Expand Up @@ -243,7 +243,7 @@ For registry-based distribution, such as using an OCI-compliant container regist
- Set up the trust policy:

```shell
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trust-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trusted-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
```

- Download the blob and signature using ORAS tool:
Expand Down Expand Up @@ -279,27 +279,27 @@ The following commands are available for managing blob trust poliies:
- Initialize blob trust policy configuration:

```shell
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trust-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trusted-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
```

- Initialize the blob trust policy configuration and set the policy specified by the `--name` flag as the global policy.

```shell
notation blob policy init --global --name "myBlobPolicy" --trust-store "ca:myCACerts" --trust-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
notation blob policy init --global --name "myBlobPolicy" --trust-store "ca:myCACerts" --trusted-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
```

- Overwrite an existing policy with a prompt:

```shell
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trust-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
notation blob policy init --name "myBlobPolicy" --trust-store "ca:myCACerts" --trusted-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
```

If the blob policy named `myBlobPolicy` has already been initialized before, running this command will prompt the user to confirm whether they want to overwrite the existing blob policy.

- Overwrite an existing policy with a prompt using the flag `--force`:

```shell
notation blob policy init --force --name "myBlobPolicy" --trust-store "ca:myCACerts" --trust-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
notation blob policy init --force --name "myBlobPolicy" --trust-store "ca:myCACerts" --trusted-identity "x509.subject:C=US,ST=WA,O=wabbit-network.io"
```

- Show the blob policy:
Expand Down
131 changes: 131 additions & 0 deletions test/e2e/suite/command/blob/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,135 @@ var _ = Describe("blob trust policy maintainer", func() {
})
})
})

When("initializing trust policy", func() {
Context("without existing policy", func() {
opts := Opts()

It("should fail when no name flag is provided", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.ExpectFailure().
Exec("blob", "policy", "init", "--trust-store", "ca:example-store", "--trusted-identity", "x509.subject: CN=example").
MatchErrKeyWords("required flag(s)", "name", "not set")
})
})

It("should fail when no trust-store flag is provided", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.ExpectFailure().
Exec("blob", "policy", "init", "--name", "example-policy", "--trusted-identity", "x509.subject: CN=example").
MatchErrKeyWords("required flag(s)", "trust-store", "not set")
})
})

It("should fail when no trusted-identity flag is provided", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.ExpectFailure().
Exec("blob", "policy", "init", "--name", "example-policy", "--trust-store", "ca:example-store").
MatchErrKeyWords("required flag(s)", "trusted-identity", "not set")
})
})

It("should fail when invalid trusted-identity format is provided", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.ExpectFailure().
Exec("blob", "policy", "init",
"--name", "example-policy",
"--trust-store", "ca:example-store",
"--trusted-identity", "invalid").
MatchErrKeyWords("invalid blob policy")
})
})

It("should fail when directory doesn't have write permission", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
// Create the notation config directory if it doesn't exist
configDir := vhost.AbsolutePath(NotationDirName)
err := os.MkdirAll(configDir, 0755)
Expect(err).NotTo(HaveOccurred())

// Remove write permissions from the directory
err = os.Chmod(configDir, 0500) // r-x for owner, no write
Expect(err).NotTo(HaveOccurred())
defer os.Chmod(configDir, 0755) // Restore permissions after test

notation.ExpectFailure().
Exec("blob", "policy", "init",
"--name", "example-policy",
"--trust-store", "ca:example-store",
"--trusted-identity", "x509.subject: C=example,ST=example,O=example").
MatchErrKeyWords("failed to write blob trust policy configuration")
})
})

It("should successfully initialize policy when all required flags are provided", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.Exec("blob", "policy", "init",
"--name", "example-policy",
"--global",
"--trust-store", "ca:example-store",
"--trust-store", "ca:example-store2",
"--trusted-identity", "x509.subject: C=example,ST=example,O=example",
"--trusted-identity", "x509.subject: C=example2,ST=example,O=example").
MatchKeyWords("Successfully initialized blob trust policy file")

// Verify the policy was created
notation.Exec("blob", "policy", "show").
MatchContent(`{
"version": "1.0",
"trustPolicies": [
{
"name": "example-policy",
"signatureVerification": {
"level": "strict"
},
"trustStores": [
"ca:example-store",
"ca:example-store2"
],
"trustedIdentities": [
"x509.subject: C=example,ST=example,O=example",
"x509.subject: C=example2,ST=example,O=example"
],
"globalPolicy": true
}
]
}`)
})
})
})

Context("with existing policy", func() {
opts := Opts(AddBlobTrustPolicyOption(validBlobTrustPolicyName))

It("should canceled when trying to initialize with existing policy", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.Exec("blob", "policy", "init",
"--name", "new-policy",
"--trust-store", "ca:new-store",
"--trusted-identity", "x509.subject: C=example,ST=example,O=example").
MatchKeyWords("The blob trust policy configuration already exists")
})
})

It("should successfully initialize policy with force flag when policy exists", func() {
Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
notation.Exec("blob", "policy", "init",
"--name", "new-policy",
"--trust-store", "ca:new-store",
"--trusted-identity", "x509.subject: C=example, ST=example, O=example",
"--force").
MatchKeyWords("Successfully initialized blob trust policy file")

// Verify the new policy was created and replaced the old one
notation.Exec("blob", "policy", "show").
MatchKeyWords(
"new-policy",
"ca:new-store",
"x509.subject: C=example, ST=example, O=example",
)
})
})
})
})
})
86 changes: 86 additions & 0 deletions test/e2e/suite/scenario/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright The Notary Project Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package scenario_test

import (
"os"
"path/filepath"

. "github.com/notaryproject/notation/test/e2e/internal/notation"
"github.com/notaryproject/notation/test/e2e/internal/utils"
. "github.com/notaryproject/notation/test/e2e/suite/common"
. "github.com/onsi/ginkgo/v2"
)

var _ = Describe("notation blob", Serial, func() {
It("signing and verifying with policy init command", func() {
Host(Opts(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) {
workDir := vhost.AbsolutePath()

// create a file to be signed
content := "hello, world"
blobPath := filepath.Join(workDir, "hello.txt")
if err := os.WriteFile(blobPath, []byte(content), 0644); err != nil {
Fail(err.Error())
}

// generate a testing key pair
notation.Exec("cert", "generate-test", "--default", "testcert").
MatchKeyWords(
"Successfully added testcert.crt to named store testcert of type ca",
"testcert: added to the key list",
)

// sign the file
notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath).
MatchKeyWords(SignSuccessfully)

// policy init
notation.Exec("blob", "policy", "init",
"--name", "testpolicy",
"--trust-store", "ca:testcert",
"--trusted-identity", "x509.subject: CN=testcert,O=Notary,L=Seattle,ST=WA,C=US").
MatchKeyWords(
"Successfully initialized blob trust policy file to",
)

notation.Exec("blob", "policy", "show").
MatchContent(`{
"version": "1.0",
"trustPolicies": [
{
"name": "testpolicy",
"signatureVerification": {
"level": "strict"
},
"trustStores": [
"ca:testcert"
],
"trustedIdentities": [
"x509.subject: CN=testcert,O=Notary,L=Seattle,ST=WA,C=US"
]
}
]
}`)

// verify the blob signature hello.txt.jws.sig
sigPath := blobPath + ".jws.sig"
notation.Exec("blob", "verify",
"--signature", sigPath,
"--policy-name", "testpolicy",
blobPath).
MatchKeyWords(VerifySuccessfully)
})
})
})
Loading