mirror of
https://github.com/vee1e/gittuf.git
synced 2026-09-04 11:47:09 +00:00
854 lines
23 KiB
Go
854 lines
23 KiB
Go
// Copyright The gittuf Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package policy
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/gittuf/gittuf/internal/dev"
|
|
"github.com/gittuf/gittuf/internal/gitinterface"
|
|
"github.com/gittuf/gittuf/internal/signerverifier/dsse"
|
|
"github.com/gittuf/gittuf/internal/signerverifier/gpg"
|
|
"github.com/gittuf/gittuf/internal/signerverifier/ssh"
|
|
artifacts "github.com/gittuf/gittuf/internal/testartifacts"
|
|
sslibdsse "github.com/gittuf/gittuf/internal/third_party/go-securesystemslib/dsse"
|
|
"github.com/gittuf/gittuf/internal/tuf"
|
|
tufv01 "github.com/gittuf/gittuf/internal/tuf/v01"
|
|
tufv02 "github.com/gittuf/gittuf/internal/tuf/v02"
|
|
)
|
|
|
|
var (
|
|
testCtx = context.Background()
|
|
rootKeyBytes = artifacts.SSHRSAPrivate
|
|
rootPubKeyBytes = artifacts.SSHRSAPublicSSH
|
|
targets1KeyBytes = artifacts.SSHECDSAPrivate
|
|
targets1PubKeyBytes = artifacts.SSHECDSAPublicSSH
|
|
targets2KeyBytes = artifacts.SSHED25519Private
|
|
targets2PubKeyBytes = artifacts.SSHED25519PublicSSH
|
|
gpgKeyBytes = artifacts.GPGKey1Private
|
|
gpgPubKeyBytes = artifacts.GPGKey1Public
|
|
gpgUnauthorizedKeyBytes = artifacts.GPGKey2Private
|
|
gpgUnauthorizedPubKeyBytes = artifacts.GPGKey2Public
|
|
)
|
|
|
|
func createTestRepository(t *testing.T, stateCreator func(*testing.T) *State) (*gitinterface.Repository, *State) {
|
|
t.Helper()
|
|
|
|
state := stateCreator(t)
|
|
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
state.repository = repo
|
|
|
|
if err := state.Commit(repo, "Create test state", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Apply(testCtx, repo, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return repo, state
|
|
}
|
|
|
|
func createTestStateWithOnlyRoot(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes) //nolint:staticcheck
|
|
key := tufv01.NewKeyFromSSLibKey(signer.MetadataKey())
|
|
|
|
rootMetadata, err := InitializeRootMetadata(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return &State{
|
|
Metadata: &StateMetadata{
|
|
RootEnvelope: rootEnv,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createTestStateWithPolicy(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
key := tufv01.NewKeyFromSSLibKey(signer.MetadataKey())
|
|
|
|
rootMetadata, err := InitializeRootMetadata(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := rootMetadata.AddPrimaryRuleFilePrincipal(key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
|
|
targetsMetadata := InitializeTargetsMetadata()
|
|
if err := targetsMetadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddRule("protect-main", []string{gpgKey.KeyID}, []string{"git:refs/heads/main"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Add a file protection rule. When used with common.AddNTestCommitsToSpecifiedRef, we have files with names 1, 2, 3,...n.
|
|
if err := targetsMetadata.AddRule("protect-files-1-and-2", []string{gpgKey.KeyID}, []string{"file:1", "file:2"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
state := &State{
|
|
Metadata: &StateMetadata{
|
|
RootEnvelope: rootEnv,
|
|
TargetsEnvelope: targetsEnv,
|
|
},
|
|
}
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
// createTestStateWithGlobalConstraintThreshold creates a policy state with no
|
|
// explicit branch protection rules but with a two-approval constraint on
|
|
// changes to the main branch. The two keys trusted are `rootPubKeyBytes` and
|
|
// `gpgPubKeyBytes`.
|
|
func createTestStateWithGlobalConstraintThreshold(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
key := tufv01.NewKeyFromSSLibKey(signer.MetadataKey())
|
|
|
|
rootMetadata, err := InitializeRootMetadata(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := rootMetadata.AddPrimaryRuleFilePrincipal(key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := rootMetadata.AddGlobalRule(tufv01.NewGlobalRuleThreshold("threshold-2-main", []string{"git:refs/heads/main"}, 2)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
|
|
targetsMetadata := InitializeTargetsMetadata()
|
|
if err := targetsMetadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
state := &State{
|
|
Metadata: &StateMetadata{
|
|
RootEnvelope: rootEnv,
|
|
TargetsEnvelope: targetsEnv,
|
|
},
|
|
}
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
// createTestStateWithGlobalConstraintBlockForcePushes creates a policy state
|
|
// with no explicit branch protection rules but with a rule that blocks force
|
|
// pushes to main.
|
|
func createTestStateWithGlobalConstraintBlockForcePushes(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
key := tufv01.NewKeyFromSSLibKey(signer.MetadataKey())
|
|
|
|
rootMetadata, err := InitializeRootMetadata(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := rootMetadata.AddPrimaryRuleFilePrincipal(key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
forcePushesGlobalRule, err := tufv01.NewGlobalRuleBlockForcePushes("block-force-pushes-main", []string{"git:refs/heads/main"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := rootMetadata.AddGlobalRule(forcePushesGlobalRule); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
|
|
targetsMetadata := InitializeTargetsMetadata()
|
|
if err := targetsMetadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
state := &State{
|
|
Metadata: &StateMetadata{
|
|
RootEnvelope: rootEnv,
|
|
TargetsEnvelope: targetsEnv,
|
|
},
|
|
}
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func createTestStateWithPolicyUsingPersons(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
key := tufv01.NewKeyFromSSLibKey(signer.MetadataKey())
|
|
|
|
rootMetadata, err := InitializeRootMetadata(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := rootMetadata.AddPrimaryRuleFilePrincipal(key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
person := &tufv02.Person{
|
|
PersonID: "jane.doe@example.com",
|
|
PublicKeys: map[string]*tufv02.Key{
|
|
gpgKey.KeyID: gpgKey,
|
|
},
|
|
}
|
|
|
|
targetsMetadata := InitializeTargetsMetadata()
|
|
if err := targetsMetadata.AddPrincipal(person); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddRule("protect-main", []string{person.PersonID}, []string{"git:refs/heads/main"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Add a file protection rule. When used with common.AddNTestCommitsToSpecifiedRef, we have files with names 1, 2, 3,...n.
|
|
if err := targetsMetadata.AddRule("protect-files-1-and-2", []string{person.PersonID}, []string{"file:1", "file:2"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
state := &State{
|
|
Metadata: &StateMetadata{
|
|
RootEnvelope: rootEnv,
|
|
TargetsEnvelope: targetsEnv,
|
|
},
|
|
}
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func createTestStateWithDelegatedPolicies(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
key := tufv01.NewKeyFromSSLibKey(signer.MetadataKey())
|
|
|
|
rootMetadata, err := InitializeRootMetadata(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := rootMetadata.AddPrimaryRuleFilePrincipal(key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
|
|
// Create the root targets metadata
|
|
targetsMetadata := InitializeTargetsMetadata()
|
|
|
|
if err := targetsMetadata.AddPrincipal(key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddRule("1", []string{key.KeyID}, []string{"file:1/*"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := targetsMetadata.AddRule("2", []string{key.KeyID}, []string{"file:2/*"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create the targets envelope
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create the second level of delegations
|
|
delegation1Metadata := InitializeTargetsMetadata()
|
|
if err := delegation1Metadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := delegation1Metadata.AddRule("3", []string{gpgKey.KeyID}, []string{"file:1/subpath1/*"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := delegation1Metadata.AddRule("4", []string{gpgKey.KeyID}, []string{"file:1/subpath2/*"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create the delegation envelope
|
|
delegation1Env, err := dsse.CreateEnvelope(delegation1Metadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
delegation1Env, err = dsse.SignEnvelope(context.Background(), delegation1Env, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
curState := &State{
|
|
Metadata: &StateMetadata{
|
|
RootEnvelope: rootEnv,
|
|
TargetsEnvelope: targetsEnv,
|
|
DelegationEnvelopes: map[string]*sslibdsse.Envelope{},
|
|
},
|
|
}
|
|
|
|
// Add the delegation envelopes to the state
|
|
|
|
curState.Metadata.DelegationEnvelopes["1"] = delegation1Env
|
|
|
|
// delegation structure
|
|
//
|
|
// targets
|
|
// /\
|
|
// 1 2
|
|
// /\
|
|
// 3 4
|
|
|
|
if err := curState.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return curState
|
|
}
|
|
|
|
func createTestStateWithThresholdPolicy(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
state := createTestStateWithPolicy(t)
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
approverKey := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, targets1PubKeyBytes))
|
|
|
|
targetsMetadata, err := state.GetTargetsMetadata(TargetsRoleName, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := targetsMetadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(approverKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set threshold = 2 for existing rule with the added key
|
|
if err := targetsMetadata.UpdateRule("protect-main", []string{gpgKey.KeyID, approverKey.KeyID}, []string{"git:refs/heads/main"}, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.TargetsEnvelope = targetsEnv
|
|
|
|
return state
|
|
}
|
|
|
|
// createTestStateWithThresholdPolicyAndGitHubAppTrust sets up a test policy
|
|
// with threshold rules. It uses v0.2 (and higher) policy metadata to support
|
|
// GitHub apps.
|
|
//
|
|
// Usage notes:
|
|
// - The app key is targets1PubKeyBytes
|
|
// - The two authorized persons are "jane.doe" and "john.doe"
|
|
// - jane.doe's signing key is gpgPubKeyBytes
|
|
// - john.doe's signing key is targets2PubKeyBytes
|
|
// - The protected namespace is the main branch
|
|
// - Use either of them as the approver for the app, with the app's signing key
|
|
// ID set as the app name
|
|
func createTestStateWithThresholdPolicyAndGitHubAppTrust(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
t.Setenv(dev.DevModeKey, "1")
|
|
t.Setenv(tufv02.AllowV02MetadataKey, "1")
|
|
|
|
state := createTestStateWithPolicyUsingPersons(t)
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
|
|
appName := tuf.GitHubAppRoleName // TODO: this should be generalized more
|
|
|
|
rootMetadata, err := state.GetRootMetadata(false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
appKey := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, targets1PubKeyBytes))
|
|
if err := rootMetadata.AddGitHubAppPrincipal(tuf.GitHubAppRoleName, appKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootMetadata.EnableGitHubAppApprovals()
|
|
state.githubAppApprovalsTrusted = true
|
|
state.githubAppKeys = []tuf.Principal{appKey}
|
|
state.githubAppRoleName = appName
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.RootEnvelope = rootEnv
|
|
|
|
targetsMetadata, err := state.GetTargetsMetadata(TargetsRoleName, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
person := &tufv02.Person{
|
|
PersonID: "jane.doe",
|
|
PublicKeys: map[string]*tufv02.Key{gpgKey.KeyID: gpgKey},
|
|
AssociatedIdentities: map[string]string{appName: "jane.doe"},
|
|
}
|
|
|
|
if err := targetsMetadata.AddPrincipal(person); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
approverKey := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, targets2PubKeyBytes))
|
|
approver := &tufv02.Person{
|
|
PersonID: "john.doe",
|
|
PublicKeys: map[string]*tufv02.Key{approverKey.KeyID: approverKey},
|
|
AssociatedIdentities: map[string]string{appName: "john.doe"},
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(approver); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set threshold = 2 for existing rule with the added key
|
|
if err := targetsMetadata.UpdateRule("protect-main", []string{person.ID(), approver.ID()}, []string{"git:refs/heads/main"}, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.TargetsEnvelope = targetsEnv
|
|
|
|
return state
|
|
}
|
|
|
|
// createTestStateWithThresholdPolicyAndGitHubAppTrustForMixedAttestations sets
|
|
// up a test policy with threshold rules. It uses v0.2 (and higher) policy
|
|
// metadata to support GitHub apps.
|
|
//
|
|
// Usage notes:
|
|
// - The app key is targets1PubKeyBytes
|
|
// - The three authorized persons are "jane.doe", "john.doe", and "jill.doe"
|
|
// - jane.doe's signing key is gpgPubKeyBytes
|
|
// - john.doe's signing key is targets2PubKeyBytes
|
|
// - jill.doe's signing key is gpgUnauthorizedPubKeyBytes
|
|
// - The protected namespace is the main branch
|
|
// - Use any of them as the approver for the app, with the app's signing key
|
|
// ID set as the app name
|
|
func createTestStateWithThresholdPolicyAndGitHubAppTrustForMixedAttestations(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
t.Setenv(dev.DevModeKey, "1")
|
|
t.Setenv(tufv02.AllowV02MetadataKey, "1")
|
|
|
|
state := createTestStateWithPolicyUsingPersons(t)
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
|
|
appName := tuf.GitHubAppRoleName // TODO: this should be generalized more
|
|
|
|
rootMetadata, err := state.GetRootMetadata(false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
appKey := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, targets1PubKeyBytes))
|
|
if err := rootMetadata.AddGitHubAppPrincipal(tuf.GitHubAppRoleName, appKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootMetadata.EnableGitHubAppApprovals()
|
|
state.githubAppApprovalsTrusted = true
|
|
state.githubAppKeys = []tuf.Principal{appKey}
|
|
state.githubAppRoleName = appName
|
|
|
|
rootEnv, err := dsse.CreateEnvelope(rootMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootEnv, err = dsse.SignEnvelope(context.Background(), rootEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.RootEnvelope = rootEnv
|
|
|
|
targetsMetadata, err := state.GetTargetsMetadata(TargetsRoleName, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
person := &tufv02.Person{
|
|
PersonID: "jane.doe",
|
|
PublicKeys: map[string]*tufv02.Key{gpgKey.KeyID: gpgKey},
|
|
AssociatedIdentities: map[string]string{appName: "jane.doe"},
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(person); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
approver1Key := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, targets2PubKeyBytes))
|
|
if err := targetsMetadata.AddPrincipal(approver1Key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
approver1 := &tufv02.Person{
|
|
PersonID: "john.doe",
|
|
PublicKeys: map[string]*tufv02.Key{approver1Key.KeyID: approver1Key},
|
|
AssociatedIdentities: map[string]string{appName: "john.doe"},
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(approver1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
approver2KeyR, err := gpg.LoadGPGKeyFromBytes(gpgUnauthorizedPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
approver2Key := tufv01.NewKeyFromSSLibKey(approver2KeyR)
|
|
approver2 := &tufv02.Person{
|
|
PersonID: "jill.doe",
|
|
PublicKeys: map[string]*tufv02.Key{approver2Key.KeyID: approver2Key},
|
|
AssociatedIdentities: map[string]string{appName: "jill.doe"},
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(approver2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set threshold = 3 for existing rule with the added principals
|
|
if err := targetsMetadata.UpdateRule("protect-main", []string{person.PersonID, approver1.PersonID, approver2.PersonID}, []string{"git:refs/heads/main"}, 3); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.TargetsEnvelope = targetsEnv
|
|
|
|
return state
|
|
}
|
|
|
|
func createTestStateWithTagPolicy(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
state := createTestStateWithPolicy(t)
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
targetsMetadata, err := state.GetTargetsMetadata(TargetsRoleName, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddRule("protect-tags", []string{gpgKey.KeyID}, []string{"git:refs/tags/*"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.TargetsEnvelope = targetsEnv
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func createTestStateWithThresholdTagPolicy(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
state := createTestStateWithPolicy(t)
|
|
|
|
gpgKeyR, err := gpg.LoadGPGKeyFromBytes(gpgPubKeyBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gpgKey := tufv01.NewKeyFromSSLibKey(gpgKeyR)
|
|
approverKey := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, targets1PubKeyBytes))
|
|
|
|
targetsMetadata, err := state.GetTargetsMetadata(TargetsRoleName, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(gpgKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(approverKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddRule("protect-tags", []string{gpgKey.KeyID, approverKey.KeyID}, []string{"git:refs/tags/*"}, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.TargetsEnvelope = targetsEnv
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func createTestStateWithTagPolicyForUnauthorizedTest(t *testing.T) *State {
|
|
t.Helper()
|
|
|
|
state := createTestStateWithPolicy(t)
|
|
|
|
rootKey := tufv01.NewKeyFromSSLibKey(ssh.NewKeyFromBytes(t, rootPubKeyBytes))
|
|
targetsMetadata, err := state.GetTargetsMetadata(TargetsRoleName, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddPrincipal(rootKey); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := targetsMetadata.AddRule("protect-tags", []string{rootKey.KeyID}, []string{"git:refs/tags/*"}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetsEnv, err := dsse.CreateEnvelope(targetsMetadata)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
signer := setupSSHKeysForSigning(t, rootKeyBytes, rootPubKeyBytes)
|
|
|
|
targetsEnv, err = dsse.SignEnvelope(context.Background(), targetsEnv, signer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Metadata.TargetsEnvelope = targetsEnv
|
|
|
|
if err := state.preprocess(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func setupSSHKeysForSigning(t *testing.T, privateBytes, publicBytes []byte) *ssh.Signer {
|
|
t.Helper()
|
|
|
|
keysDir := t.TempDir()
|
|
privKeyPath := filepath.Join(keysDir, "key")
|
|
pubKeyPath := filepath.Join(keysDir, "key.pub")
|
|
|
|
if err := os.WriteFile(privKeyPath, privateBytes, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(pubKeyPath, publicBytes, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
signer, err := ssh.NewSignerFromFile(privKeyPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return signer
|
|
}
|