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  }