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