code.gitea.io/gitea@v1.22.3/tests/integration/gpg_git_test.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "net/url" 10 "os" 11 "testing" 12 13 auth_model "code.gitea.io/gitea/models/auth" 14 "code.gitea.io/gitea/models/unittest" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/process" 17 "code.gitea.io/gitea/modules/setting" 18 api "code.gitea.io/gitea/modules/structs" 19 "code.gitea.io/gitea/modules/test" 20 "code.gitea.io/gitea/tests" 21 22 "github.com/stretchr/testify/assert" 23 "golang.org/x/crypto/openpgp" 24 "golang.org/x/crypto/openpgp/armor" 25 ) 26 27 func TestGPGGit(t *testing.T) { 28 tmpDir := t.TempDir() // use a temp dir to avoid messing with the user's GPG keyring 29 err := os.Chmod(tmpDir, 0o700) 30 assert.NoError(t, err) 31 32 oldGNUPGHome := os.Getenv("GNUPGHOME") 33 err = os.Setenv("GNUPGHOME", tmpDir) 34 assert.NoError(t, err) 35 defer os.Setenv("GNUPGHOME", oldGNUPGHome) 36 37 // Need to create a root key 38 rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local") 39 if !assert.NoError(t, err, "importTestingKey") { 40 return 41 } 42 43 defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())() 44 defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")() 45 defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")() 46 defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})() 47 defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})() 48 49 username := "user2" 50 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username}) 51 baseAPITestContext := NewAPITestContext(t, username, "repo1") 52 53 onGiteaRun(t, func(t *testing.T, u *url.URL) { 54 u.Path = baseAPITestContext.GitPath() 55 56 t.Run("Unsigned-Initial", func(t *testing.T) { 57 defer tests.PrintCurrentTest(t)() 58 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 59 t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) 60 t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { 61 assert.NotNil(t, branch.Commit) 62 assert.NotNil(t, branch.Commit.Verification) 63 assert.False(t, branch.Commit.Verification.Verified) 64 assert.Empty(t, branch.Commit.Verification.Signature) 65 })) 66 t.Run("CreateCRUDFile-Never", crudActionCreateFile( 67 t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { 68 assert.False(t, response.Verification.Verified) 69 })) 70 t.Run("CreateCRUDFile-Never", crudActionCreateFile( 71 t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) { 72 assert.False(t, response.Verification.Verified) 73 })) 74 }) 75 76 setting.Repository.Signing.CRUDActions = []string{"parentsigned"} 77 t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) { 78 defer tests.PrintCurrentTest(t)() 79 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 80 t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( 81 t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { 82 assert.False(t, response.Verification.Verified) 83 })) 84 t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( 85 t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) { 86 assert.False(t, response.Verification.Verified) 87 })) 88 }) 89 90 setting.Repository.Signing.CRUDActions = []string{"never"} 91 t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) { 92 defer tests.PrintCurrentTest(t)() 93 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 94 t.Run("CreateCRUDFile-Never", crudActionCreateFile( 95 t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) { 96 assert.False(t, response.Verification.Verified) 97 })) 98 }) 99 100 setting.Repository.Signing.CRUDActions = []string{"always"} 101 t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) { 102 defer tests.PrintCurrentTest(t)() 103 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 104 t.Run("CreateCRUDFile-Always", crudActionCreateFile( 105 t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { 106 assert.NotNil(t, response.Verification) 107 if response.Verification == nil { 108 assert.FailNow(t, "no verification provided with response! %v", response) 109 } 110 assert.True(t, response.Verification.Verified) 111 if !response.Verification.Verified { 112 t.FailNow() 113 } 114 assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) 115 })) 116 t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile( 117 t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) { 118 assert.NotNil(t, response.Verification) 119 if response.Verification == nil { 120 assert.FailNow(t, "no verification provided with response! %v", response) 121 } 122 assert.True(t, response.Verification.Verified) 123 if !response.Verification.Verified { 124 t.FailNow() 125 } 126 assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) 127 })) 128 }) 129 130 setting.Repository.Signing.CRUDActions = []string{"parentsigned"} 131 t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) { 132 defer tests.PrintCurrentTest(t)() 133 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 134 t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile( 135 t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) { 136 assert.NotNil(t, response.Verification) 137 if response.Verification == nil { 138 assert.FailNow(t, "no verification provided with response! %v", response) 139 } 140 assert.True(t, response.Verification.Verified) 141 if !response.Verification.Verified { 142 t.FailNow() 143 } 144 assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) 145 })) 146 }) 147 148 setting.Repository.Signing.InitialCommit = []string{"always"} 149 t.Run("AlwaysSign-Initial", func(t *testing.T) { 150 defer tests.PrintCurrentTest(t)() 151 testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 152 t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) 153 t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { 154 assert.NotNil(t, branch.Commit) 155 if branch.Commit == nil { 156 assert.FailNow(t, "no commit provided with branch! %v", branch) 157 } 158 assert.NotNil(t, branch.Commit.Verification) 159 if branch.Commit.Verification == nil { 160 assert.FailNow(t, "no verification provided with branch commit! %v", branch.Commit) 161 } 162 assert.True(t, branch.Commit.Verification.Verified) 163 if !branch.Commit.Verification.Verified { 164 t.FailNow() 165 } 166 assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email) 167 })) 168 }) 169 170 setting.Repository.Signing.CRUDActions = []string{"never"} 171 t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) { 172 defer tests.PrintCurrentTest(t)() 173 testCtx := NewAPITestContext(t, username, "initial-always-never", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 174 t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) 175 t.Run("CreateCRUDFile-Never", crudActionCreateFile( 176 t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { 177 assert.False(t, response.Verification.Verified) 178 })) 179 }) 180 181 setting.Repository.Signing.CRUDActions = []string{"parentsigned"} 182 t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) { 183 defer tests.PrintCurrentTest(t)() 184 testCtx := NewAPITestContext(t, username, "initial-always-parent", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 185 t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) 186 t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( 187 t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { 188 assert.True(t, response.Verification.Verified) 189 if !response.Verification.Verified { 190 t.FailNow() 191 return 192 } 193 assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) 194 })) 195 }) 196 197 setting.Repository.Signing.CRUDActions = []string{"always"} 198 t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) { 199 defer tests.PrintCurrentTest(t)() 200 testCtx := NewAPITestContext(t, username, "initial-always-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 201 t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) 202 t.Run("CreateCRUDFile-Always", crudActionCreateFile( 203 t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { 204 assert.True(t, response.Verification.Verified) 205 if !response.Verification.Verified { 206 t.FailNow() 207 return 208 } 209 assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) 210 })) 211 }) 212 213 setting.Repository.Signing.Merges = []string{"commitssigned"} 214 t.Run("UnsignedMerging", func(t *testing.T) { 215 defer tests.PrintCurrentTest(t)() 216 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 217 t.Run("CreatePullRequest", func(t *testing.T) { 218 pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t) 219 assert.NoError(t, err) 220 t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) 221 }) 222 t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { 223 assert.NotNil(t, branch.Commit) 224 assert.NotNil(t, branch.Commit.Verification) 225 assert.False(t, branch.Commit.Verification.Verified) 226 assert.Empty(t, branch.Commit.Verification.Signature) 227 })) 228 }) 229 230 setting.Repository.Signing.Merges = []string{"basesigned"} 231 t.Run("BaseSignedMerging", func(t *testing.T) { 232 defer tests.PrintCurrentTest(t)() 233 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 234 t.Run("CreatePullRequest", func(t *testing.T) { 235 pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t) 236 assert.NoError(t, err) 237 t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) 238 }) 239 t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { 240 assert.NotNil(t, branch.Commit) 241 assert.NotNil(t, branch.Commit.Verification) 242 assert.False(t, branch.Commit.Verification.Verified) 243 assert.Empty(t, branch.Commit.Verification.Signature) 244 })) 245 }) 246 247 setting.Repository.Signing.Merges = []string{"commitssigned"} 248 t.Run("CommitsSignedMerging", func(t *testing.T) { 249 defer tests.PrintCurrentTest(t)() 250 testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 251 t.Run("CreatePullRequest", func(t *testing.T) { 252 pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t) 253 assert.NoError(t, err) 254 t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) 255 }) 256 t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { 257 assert.NotNil(t, branch.Commit) 258 assert.NotNil(t, branch.Commit.Verification) 259 assert.True(t, branch.Commit.Verification.Verified) 260 })) 261 }) 262 }) 263 } 264 265 func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { 266 return doAPICreateFile(ctx, path, &api.CreateFileOptions{ 267 FileOptions: api.FileOptions{ 268 BranchName: from, 269 NewBranchName: to, 270 Message: fmt.Sprintf("from:%s to:%s path:%s", from, to, path), 271 Author: api.Identity{ 272 Name: user.FullName, 273 Email: user.Email, 274 }, 275 Committer: api.Identity{ 276 Name: user.FullName, 277 Email: user.Email, 278 }, 279 }, 280 ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))), 281 }, callback...) 282 } 283 284 func importTestingKey(tmpDir, name, email string) (*openpgp.Entity, error) { 285 if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil { 286 return nil, err 287 } 288 keyringFile, err := os.Open("tests/integration/private-testing.key") 289 if err != nil { 290 return nil, err 291 } 292 defer keyringFile.Close() 293 294 block, err := armor.Decode(keyringFile) 295 if err != nil { 296 return nil, err 297 } 298 299 keyring, err := openpgp.ReadKeyRing(block.Body) 300 if err != nil { 301 return nil, fmt.Errorf("Keyring access failed: '%w'", err) 302 } 303 304 // There should only be one entity in this file. 305 return keyring[0], nil 306 }