github.com/argoproj/argo-cd/v3@v3.2.1/util/gpg/gpg_test.go (about) 1 package gpg 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "os/exec" 8 "path" 9 "testing" 10 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 "github.com/argoproj/argo-cd/v3/common" 15 "github.com/argoproj/argo-cd/v3/test" 16 ) 17 18 const ( 19 longKeyID = "5DE3E0509C47EA3CF04A42D34AEE18F83AFDEB23" 20 shortKeyID = "4AEE18F83AFDEB23" 21 ) 22 23 var syncTestSources = map[string]string{ 24 "F7842A5CEAA9C0B1": "testdata/janedoe.asc", 25 "FDC79815400D88A9": "testdata/johndoe.asc", 26 "4AEE18F83AFDEB23": "testdata/github.asc", 27 } 28 29 // Helper function to create temporary GNUPGHOME 30 func initTempDir(t *testing.T) string { 31 t.Helper() 32 // Intentionally avoid using t.TempDir. That function creates really long paths, which can exceed the socket file 33 // path length on some OSes. The GPG tests rely on sockets. 34 p, err := os.MkdirTemp(os.TempDir(), "") 35 if err != nil { 36 panic(err) 37 } 38 fmt.Printf("-> Using %s as GNUPGHOME\n", p) 39 t.Setenv(common.EnvGnuPGHome, p) 40 t.Cleanup(func() { 41 err := os.RemoveAll(p) 42 if err != nil { 43 panic(err) 44 } 45 }) 46 return p 47 } 48 49 func Test_IsGPGEnabled(t *testing.T) { 50 t.Run("true", func(t *testing.T) { 51 t.Setenv("ARGOCD_GPG_ENABLED", "true") 52 assert.True(t, IsGPGEnabled()) 53 }) 54 55 t.Run("false", func(t *testing.T) { 56 t.Setenv("ARGOCD_GPG_ENABLED", "false") 57 assert.False(t, IsGPGEnabled()) 58 }) 59 60 t.Run("empty", func(t *testing.T) { 61 t.Setenv("ARGOCD_GPG_ENABLED", "") 62 assert.True(t, IsGPGEnabled()) 63 }) 64 } 65 66 func Test_GPG_InitializeGnuPG(t *testing.T) { 67 p := initTempDir(t) 68 69 // First run should initialize fine 70 err := InitializeGnuPG() 71 require.NoError(t, err) 72 73 // We should have exactly one public key with ultimate trust (our own) in the keyring 74 keys, err := GetInstalledPGPKeys(nil) 75 require.NoError(t, err) 76 assert.Len(t, keys, 1) 77 assert.Equal(t, "ultimate", keys[0].Trust) 78 79 // During unit-tests, we need to also kill gpg-agent so we can create a new key. 80 // In real world scenario -- i.e. container crash -- gpg-agent is not running yet. 81 cmd := exec.Command("gpgconf", "--kill", "gpg-agent") 82 cmd.Env = []string{"GNUPGHOME=" + p} 83 err = cmd.Run() 84 require.NoError(t, err) 85 86 // Second run should not return error 87 err = InitializeGnuPG() 88 require.NoError(t, err) 89 keys, err = GetInstalledPGPKeys(nil) 90 require.NoError(t, err) 91 assert.Len(t, keys, 1) 92 assert.Equal(t, "ultimate", keys[0].Trust) 93 94 t.Run("GNUPGHOME is a file", func(t *testing.T) { 95 f, err := os.CreateTemp(t.TempDir(), "gpg-test") 96 require.NoError(t, err) 97 defer os.Remove(f.Name()) 98 99 // we need to error out 100 t.Setenv(common.EnvGnuPGHome, f.Name()) 101 err = InitializeGnuPG() 102 assert.ErrorContains(t, err, "does not point to a directory") 103 }) 104 105 t.Run("Unaccessible GNUPGHOME", func(t *testing.T) { 106 p := initTempDir(t) 107 fp := p + "/gpg" 108 err = os.Mkdir(fp, 0o000) 109 if err != nil { 110 panic(err.Error()) 111 } 112 if err != nil { 113 panic(err.Error()) 114 } 115 t.Setenv(common.EnvGnuPGHome, fp) 116 err := InitializeGnuPG() 117 require.Error(t, err) 118 // Restore permissions so path can be deleted 119 err = os.Chmod(fp, 0o700) 120 if err != nil { 121 panic(err.Error()) 122 } 123 }) 124 125 t.Run("GNUPGHOME with too wide permissions", func(t *testing.T) { 126 // We do not expect an error here, because of openshift's random UIDs that 127 // forced us to use an emptyDir mount (#4127) 128 p := initTempDir(t) 129 err := os.Chmod(p, 0o777) 130 if err != nil { 131 panic(err.Error()) 132 } 133 t.Setenv(common.EnvGnuPGHome, p) 134 err = InitializeGnuPG() 135 require.NoError(t, err) 136 }) 137 } 138 139 func Test_GPG_KeyManagement(t *testing.T) { 140 initTempDir(t) 141 142 err := InitializeGnuPG() 143 require.NoError(t, err) 144 145 // Import a single good key 146 keys, err := ImportPGPKeys("testdata/github.asc") 147 require.NoError(t, err) 148 assert.Len(t, keys, 1) 149 assert.Equal(t, "4AEE18F83AFDEB23", keys[0].KeyID) 150 assert.Contains(t, keys[0].Owner, "noreply@github.com") 151 assert.Equal(t, "unknown", keys[0].Trust) 152 assert.Equal(t, "unknown", keys[0].SubType) 153 154 kids := make([]string, 0) 155 importedKeyId := keys[0].KeyID 156 157 // We should have a total of 2 keys in the keyring now 158 { 159 keys, err := GetInstalledPGPKeys(nil) 160 require.NoError(t, err) 161 assert.Len(t, keys, 2) 162 } 163 164 // We should now have that key in our keyring with unknown trust (trustdb not updated) 165 { 166 keys, err := GetInstalledPGPKeys([]string{importedKeyId}) 167 require.NoError(t, err) 168 assert.Len(t, keys, 1) 169 assert.Equal(t, "4AEE18F83AFDEB23", keys[0].KeyID) 170 assert.Contains(t, keys[0].Owner, "noreply@github.com") 171 assert.Equal(t, "unknown", keys[0].Trust) 172 assert.Equal(t, "rsa2048", keys[0].SubType) 173 kids = append(kids, keys[0].Fingerprint) 174 } 175 176 assert.Len(t, kids, 1) 177 178 // Set trust level for our key and check the result 179 { 180 err := SetPGPTrustLevelById(kids, "ultimate") 181 require.NoError(t, err) 182 keys, err := GetInstalledPGPKeys(kids) 183 require.NoError(t, err) 184 assert.Len(t, keys, 1) 185 assert.Equal(t, kids[0], keys[0].Fingerprint) 186 assert.Equal(t, "ultimate", keys[0].Trust) 187 } 188 189 // Import garbage - error expected 190 keys, err = ImportPGPKeys("testdata/garbage.asc") 191 require.Error(t, err) 192 assert.Empty(t, keys) 193 194 // We should still have a total of 2 keys in the keyring now 195 { 196 keys, err := GetInstalledPGPKeys(nil) 197 require.NoError(t, err) 198 assert.Len(t, keys, 2) 199 } 200 201 // Delete previously imported public key 202 { 203 err := DeletePGPKey(importedKeyId) 204 require.NoError(t, err) 205 keys, err := GetInstalledPGPKeys(nil) 206 require.NoError(t, err) 207 assert.Len(t, keys, 1) 208 } 209 210 // Delete non-existing key 211 { 212 err := DeletePGPKey(importedKeyId) 213 require.Error(t, err) 214 } 215 216 // Import multiple keys 217 { 218 keys, err := ImportPGPKeys("testdata/multi.asc") 219 require.NoError(t, err) 220 assert.Len(t, keys, 2) 221 assert.Contains(t, keys[0].Owner, "john.doe@example.com") 222 assert.Contains(t, keys[1].Owner, "jane.doe@example.com") 223 } 224 225 // Check if they were really imported 226 { 227 keys, err := GetInstalledPGPKeys(nil) 228 require.NoError(t, err) 229 assert.Len(t, keys, 3) 230 } 231 } 232 233 func Test_ImportPGPKeysFromString(t *testing.T) { 234 initTempDir(t) 235 236 err := InitializeGnuPG() 237 require.NoError(t, err) 238 239 // Import a single good key 240 keys, err := ImportPGPKeysFromString(test.MustLoadFileToString("testdata/github.asc")) 241 require.NoError(t, err) 242 assert.Len(t, keys, 1) 243 assert.Equal(t, "4AEE18F83AFDEB23", keys[0].KeyID) 244 assert.Contains(t, keys[0].Owner, "noreply@github.com") 245 assert.Equal(t, "unknown", keys[0].Trust) 246 assert.Equal(t, "unknown", keys[0].SubType) 247 } 248 249 func Test_ValidateGPGKeysFromString(t *testing.T) { 250 initTempDir(t) 251 252 err := InitializeGnuPG() 253 require.NoError(t, err) 254 255 { 256 keyData := test.MustLoadFileToString("testdata/github.asc") 257 keys, err := ValidatePGPKeysFromString(keyData) 258 require.NoError(t, err) 259 assert.Len(t, keys, 1) 260 } 261 262 { 263 keyData := test.MustLoadFileToString("testdata/multi.asc") 264 keys, err := ValidatePGPKeysFromString(keyData) 265 require.NoError(t, err) 266 assert.Len(t, keys, 2) 267 } 268 } 269 270 func Test_ValidateGPGKeys(t *testing.T) { 271 initTempDir(t) 272 273 err := InitializeGnuPG() 274 require.NoError(t, err) 275 276 // Validation good case - 1 key 277 { 278 keys, err := ValidatePGPKeys("testdata/github.asc") 279 require.NoError(t, err) 280 assert.Len(t, keys, 1) 281 assert.Contains(t, keys, "4AEE18F83AFDEB23") 282 } 283 284 // Validation bad case 285 { 286 keys, err := ValidatePGPKeys("testdata/garbage.asc") 287 require.Error(t, err) 288 assert.Empty(t, keys) 289 } 290 291 // We should still have a total of 1 keys in the keyring now 292 { 293 keys, err := GetInstalledPGPKeys(nil) 294 require.NoError(t, err) 295 assert.Len(t, keys, 1) 296 } 297 } 298 299 func Test_GPG_ParseGitCommitVerification(t *testing.T) { 300 initTempDir(t) 301 302 err := InitializeGnuPG() 303 require.NoError(t, err) 304 305 keys, err := ImportPGPKeys("testdata/github.asc") 306 require.NoError(t, err) 307 assert.Len(t, keys, 1) 308 309 // Good case 310 { 311 c, err := os.ReadFile("testdata/good_signature.txt") 312 if err != nil { 313 panic(err.Error()) 314 } 315 res := ParseGitCommitVerification(string(c)) 316 assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) 317 assert.Equal(t, "RSA", res.Cipher) 318 assert.Equal(t, "ultimate", res.Trust) 319 assert.Equal(t, "Wed Feb 26 23:22:34 2020 CET", res.Date) 320 assert.Equal(t, VerifyResultGood, res.Result) 321 } 322 323 // Signature with unknown key - considered invalid 324 { 325 c, err := os.ReadFile("testdata/unknown_signature1.txt") 326 if err != nil { 327 panic(err.Error()) 328 } 329 res := ParseGitCommitVerification(string(c)) 330 assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) 331 assert.Equal(t, "RSA", res.Cipher) 332 assert.Equal(t, TrustUnknown, res.Trust) 333 assert.Equal(t, "Mon Aug 26 20:59:48 2019 CEST", res.Date) 334 assert.Equal(t, VerifyResultInvalid, res.Result) 335 } 336 337 // Signature with unknown key and additional fields - considered invalid 338 { 339 c, err := os.ReadFile("testdata/unknown_signature2.txt") 340 if err != nil { 341 panic(err.Error()) 342 } 343 res := ParseGitCommitVerification(string(c)) 344 assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) 345 assert.Equal(t, "RSA", res.Cipher) 346 assert.Equal(t, TrustUnknown, res.Trust) 347 assert.Equal(t, "Mon Aug 26 20:59:48 2019 CEST", res.Date) 348 assert.Equal(t, VerifyResultInvalid, res.Result) 349 } 350 351 // Bad signature with known key 352 { 353 c, err := os.ReadFile("testdata/bad_signature_bad.txt") 354 if err != nil { 355 panic(err.Error()) 356 } 357 res := ParseGitCommitVerification(string(c)) 358 assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) 359 assert.Equal(t, "RSA", res.Cipher) 360 assert.Equal(t, "ultimate", res.Trust) 361 assert.Equal(t, "Wed Feb 26 23:22:34 2020 CET", res.Date) 362 assert.Equal(t, VerifyResultBad, res.Result) 363 } 364 365 // Bad case: Manipulated/invalid clear text signature 366 { 367 c, err := os.ReadFile("testdata/bad_signature_manipulated.txt") 368 if err != nil { 369 panic(err.Error()) 370 } 371 res := ParseGitCommitVerification(string(c)) 372 assert.Equal(t, VerifyResultUnknown, res.Result) 373 assert.Contains(t, res.Message, "Could not parse output") 374 } 375 376 // Bad case: Incomplete signature data #1 377 { 378 c, err := os.ReadFile("testdata/bad_signature_preeof1.txt") 379 if err != nil { 380 panic(err.Error()) 381 } 382 res := ParseGitCommitVerification(string(c)) 383 assert.Equal(t, VerifyResultUnknown, res.Result) 384 assert.Contains(t, res.Message, "end-of-file") 385 } 386 387 // Bad case: Incomplete signature data #2 388 { 389 c, err := os.ReadFile("testdata/bad_signature_preeof2.txt") 390 if err != nil { 391 panic(err.Error()) 392 } 393 res := ParseGitCommitVerification(string(c)) 394 assert.Equal(t, VerifyResultUnknown, res.Result) 395 assert.Contains(t, res.Message, "end-of-file") 396 } 397 398 // Bad case: No signature data #1 399 { 400 c, err := os.ReadFile("testdata/bad_signature_nodata.txt") 401 if err != nil { 402 panic(err.Error()) 403 } 404 res := ParseGitCommitVerification(string(c)) 405 assert.Equal(t, VerifyResultUnknown, res.Result) 406 assert.Contains(t, res.Message, "no verification data found") 407 } 408 409 // Bad case: Malformed signature data #1 410 { 411 c, err := os.ReadFile("testdata/bad_signature_malformed1.txt") 412 if err != nil { 413 panic(err.Error()) 414 } 415 res := ParseGitCommitVerification(string(c)) 416 assert.Equal(t, VerifyResultUnknown, res.Result) 417 assert.Contains(t, res.Message, "no verification data found") 418 } 419 420 // Bad case: Malformed signature data #2 421 { 422 c, err := os.ReadFile("testdata/bad_signature_malformed2.txt") 423 if err != nil { 424 panic(err.Error()) 425 } 426 res := ParseGitCommitVerification(string(c)) 427 assert.Equal(t, VerifyResultUnknown, res.Result) 428 assert.Contains(t, res.Message, "Could not parse key ID") 429 } 430 431 // Bad case: Malformed signature data #3 432 { 433 c, err := os.ReadFile("testdata/bad_signature_malformed3.txt") 434 if err != nil { 435 panic(err.Error()) 436 } 437 res := ParseGitCommitVerification(string(c)) 438 assert.Equal(t, VerifyResultUnknown, res.Result) 439 assert.Contains(t, res.Message, "Could not parse result of verify") 440 } 441 442 // Bad case: Invalid key ID in signature 443 { 444 c, err := os.ReadFile("testdata/bad_signature_badkeyid.txt") 445 if err != nil { 446 panic(err.Error()) 447 } 448 res := ParseGitCommitVerification(string(c)) 449 assert.Equal(t, VerifyResultUnknown, res.Result) 450 assert.Contains(t, res.Message, "Invalid PGP key ID") 451 } 452 } 453 454 func Test_GetGnuPGHomePath(t *testing.T) { 455 t.Run("empty", func(t *testing.T) { 456 t.Setenv(common.EnvGnuPGHome, "") 457 p := common.GetGnuPGHomePath() 458 assert.Equal(t, common.DefaultGnuPgHomePath, p) 459 }) 460 461 t.Run("tempdir", func(t *testing.T) { 462 t.Setenv(common.EnvGnuPGHome, "/tmp/gpghome") 463 p := common.GetGnuPGHomePath() 464 assert.Equal(t, "/tmp/gpghome", p) 465 }) 466 } 467 468 func Test_KeyID(t *testing.T) { 469 // Good case - long key ID (aka fingerprint) to short key ID 470 { 471 res := KeyID(longKeyID) 472 assert.Equal(t, shortKeyID, res) 473 } 474 // Good case - short key ID remains same 475 { 476 res := KeyID(shortKeyID) 477 assert.Equal(t, shortKeyID, res) 478 } 479 // Bad case - key ID too short 480 { 481 keyID := "AEE18F83AFDEB23" 482 res := KeyID(keyID) 483 assert.Empty(t, res) 484 } 485 // Bad case - key ID too long 486 { 487 keyID := "5DE3E0509C47EA3CF04A42D34AEE18F83AFDEB2323" 488 res := KeyID(keyID) 489 assert.Empty(t, res) 490 } 491 // Bad case - right length, but not hex string 492 { 493 keyID := "abcdefghijklmn" 494 res := KeyID(keyID) 495 assert.Empty(t, res) 496 } 497 } 498 499 func Test_IsShortKeyID(t *testing.T) { 500 assert.True(t, IsShortKeyID(shortKeyID)) 501 assert.False(t, IsShortKeyID(longKeyID)) 502 assert.False(t, IsShortKeyID("ab")) 503 } 504 505 func Test_IsLongKeyID(t *testing.T) { 506 assert.True(t, IsLongKeyID(longKeyID)) 507 assert.False(t, IsLongKeyID(shortKeyID)) 508 assert.False(t, IsLongKeyID(longKeyID+"a")) 509 } 510 511 func Test_isHexString(t *testing.T) { 512 assert.True(t, isHexString("ab0099")) 513 assert.True(t, isHexString("AB0099")) 514 assert.False(t, isHexString("foobar")) 515 } 516 517 func Test_IsSecretKey(t *testing.T) { 518 initTempDir(t) 519 520 // First run should initialize fine 521 err := InitializeGnuPG() 522 require.NoError(t, err) 523 524 // We should have exactly one public key with ultimate trust (our own) in the keyring 525 keys, err := GetInstalledPGPKeys(nil) 526 require.NoError(t, err) 527 assert.Len(t, keys, 1) 528 assert.Equal(t, "ultimate", keys[0].Trust) 529 530 { 531 secret, err := IsSecretKey(keys[0].KeyID) 532 require.NoError(t, err) 533 assert.True(t, secret) 534 } 535 536 { 537 secret, err := IsSecretKey("invalid") 538 require.NoError(t, err) 539 assert.False(t, secret) 540 } 541 } 542 543 func Test_SyncKeyRingFromDirectory(t *testing.T) { 544 initTempDir(t) 545 546 // First run should initialize fine 547 err := InitializeGnuPG() 548 require.NoError(t, err) 549 550 tempDir := t.TempDir() 551 552 { 553 newKeys, removed, err := SyncKeyRingFromDirectory(tempDir) 554 require.NoError(t, err) 555 assert.Empty(t, newKeys) 556 assert.Empty(t, removed) 557 } 558 559 { 560 for k, v := range syncTestSources { 561 src, err := os.Open(v) 562 if err != nil { 563 panic(err.Error()) 564 } 565 defer src.Close() 566 dst, err := os.Create(path.Join(tempDir, k)) 567 if err != nil { 568 panic(err.Error()) 569 } 570 defer dst.Close() 571 _, err = io.Copy(dst, src) 572 if err != nil { 573 panic(err.Error()) 574 } 575 dst.Close() 576 } 577 578 newKeys, removed, err := SyncKeyRingFromDirectory(tempDir) 579 require.NoError(t, err) 580 assert.Len(t, newKeys, 3) 581 assert.Empty(t, removed) 582 583 installed, err := GetInstalledPGPKeys(newKeys) 584 require.NoError(t, err) 585 for _, k := range installed { 586 assert.Contains(t, newKeys, k.KeyID) 587 } 588 } 589 590 { 591 err := os.Remove(path.Join(tempDir, "4AEE18F83AFDEB23")) 592 if err != nil { 593 panic(err.Error()) 594 } 595 newKeys, removed, err := SyncKeyRingFromDirectory(tempDir) 596 require.NoError(t, err) 597 assert.Empty(t, newKeys) 598 assert.Len(t, removed, 1) 599 600 installed, err := GetInstalledPGPKeys(newKeys) 601 require.NoError(t, err) 602 for _, k := range installed { 603 assert.NotEqual(t, k.KeyID, removed[0]) 604 } 605 } 606 }