github.com/greenpau/go-authcrunch@v1.1.4/pkg/kms/config_test.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kms
    16  
    17  import (
    18  	"fmt"
    19  	"github.com/google/go-cmp/cmp"
    20  	"github.com/greenpau/go-authcrunch/internal/tests"
    21  	"github.com/greenpau/go-authcrunch/pkg/errors"
    22  	"os"
    23  	"testing"
    24  )
    25  
    26  func TestValidateCryptoKeyConfig(t *testing.T) {
    27  	var testcases = []struct {
    28  		name      string
    29  		config    *CryptoKeyConfig
    30  		shouldErr bool
    31  		err       error
    32  	}{
    33  		{
    34  			name: "default shared key in default context for verify",
    35  			config: &CryptoKeyConfig{
    36  				ID:            "0",
    37  				Usage:         "verify",
    38  				TokenName:     "foobar token",
    39  				Source:        "config",
    40  				Algorithm:     "hmac",
    41  				Secret:        "foobar",
    42  				TokenLifetime: 900,
    43  				parsed:        true,
    44  			},
    45  		},
    46  		{
    47  			name: "invalid key usage",
    48  			config: &CryptoKeyConfig{
    49  				ID:            "0",
    50  				Usage:         "both",
    51  				TokenName:     "foobar token",
    52  				Source:        "config",
    53  				Algorithm:     "hmac",
    54  				Secret:        "foobar",
    55  				TokenLifetime: 900,
    56  				parsed:        true,
    57  			},
    58  			shouldErr: true,
    59  			err:       fmt.Errorf("key usage %q is invalid", "both"),
    60  		},
    61  		{
    62  			name: "empty key usage",
    63  			config: &CryptoKeyConfig{
    64  				ID:            "0",
    65  				TokenName:     "foobar token",
    66  				Source:        "config",
    67  				Algorithm:     "hmac",
    68  				Secret:        "foobar",
    69  				TokenLifetime: 900,
    70  				parsed:        true,
    71  			},
    72  			shouldErr: true,
    73  			err:       fmt.Errorf("key usage is not set"),
    74  		},
    75  		{
    76  			name: "invalid key source",
    77  			config: &CryptoKeyConfig{
    78  				ID:            "0",
    79  				Usage:         "verify",
    80  				TokenName:     "foobar token",
    81  				Source:        "foo",
    82  				Algorithm:     "hmac",
    83  				Secret:        "foobar",
    84  				TokenLifetime: 900,
    85  				parsed:        true,
    86  			},
    87  			shouldErr: true,
    88  			err:       fmt.Errorf("key source %q is invalid", "foo"),
    89  		},
    90  		{
    91  			name: "empty key source",
    92  			config: &CryptoKeyConfig{
    93  				ID:            "0",
    94  				Usage:         "verify",
    95  				TokenName:     "foobar token",
    96  				Algorithm:     "hmac",
    97  				Secret:        "foobar",
    98  				TokenLifetime: 900,
    99  				parsed:        true,
   100  			},
   101  			shouldErr: true,
   102  			err:       fmt.Errorf("key source not found"),
   103  		},
   104  		{
   105  			name: "invalid key algo",
   106  			config: &CryptoKeyConfig{
   107  				ID:            "0",
   108  				Usage:         "verify",
   109  				TokenName:     "foobar token",
   110  				Source:        "config",
   111  				Algorithm:     "foo",
   112  				Secret:        "foobar",
   113  				TokenLifetime: 900,
   114  				parsed:        true,
   115  			},
   116  			shouldErr: true,
   117  			err:       fmt.Errorf("key algorithm %q is invalid", "foo"),
   118  		},
   119  		{
   120  			name: "empty source type for env",
   121  			config: &CryptoKeyConfig{
   122  				ID:            "cb315f43c868",
   123  				Usage:         "verify",
   124  				Source:        "env",
   125  				EnvVarName:    "JWT_SECRET_KEY",
   126  				TokenName:     "access_token",
   127  				TokenLifetime: 900,
   128  				parsed:        true,
   129  				validated:     true,
   130  			},
   131  			shouldErr: true,
   132  			err:       fmt.Errorf("key source type for env not set"),
   133  		},
   134  		{
   135  			name: "invalid source type for env",
   136  			config: &CryptoKeyConfig{
   137  				ID:            "cb315f43c868",
   138  				Usage:         "verify",
   139  				Source:        "env",
   140  				EnvVarName:    "JWT_SECRET_KEY",
   141  				EnvVarType:    "foo",
   142  				TokenName:     "access_token",
   143  				TokenLifetime: 900,
   144  				parsed:        true,
   145  				validated:     true,
   146  			},
   147  			shouldErr: true,
   148  			err:       fmt.Errorf("key source type %q for env is invalid", "foo"),
   149  		},
   150  	}
   151  	for _, tc := range testcases {
   152  		t.Run(tc.name, func(t *testing.T) {
   153  			msgs := []string{fmt.Sprintf("test name: %s", tc.name)}
   154  			msgs = append(msgs, fmt.Sprintf("config: %v", tc.config))
   155  			err := tc.config.validate()
   156  			if tests.EvalErrWithLog(t, err, nil, tc.shouldErr, tc.err, msgs) {
   157  				return
   158  			}
   159  		})
   160  	}
   161  }
   162  
   163  func TestParseCryptoKeyConfigs(t *testing.T) {
   164  	var testcases = []struct {
   165  		name      string
   166  		config    string
   167  		env       map[string]string
   168  		want      map[string]interface{}
   169  		shouldErr bool
   170  		err       error
   171  	}{
   172  		{
   173  			name: "default shared key in default context for verify",
   174  			config: `
   175  			    crypto default token lifetime 2400
   176                  crypto key token name "foobar token"
   177                  crypto key verify foobar
   178              `,
   179  			want: map[string]interface{}{
   180  				"config_count": 1,
   181  				"configs": []*CryptoKeyConfig{
   182  					{
   183  						ID:            "0",
   184  						Usage:         "verify",
   185  						TokenName:     "foobar token",
   186  						Source:        "config",
   187  						Algorithm:     "hmac",
   188  						Secret:        "foobar",
   189  						TokenLifetime: 2400,
   190  						parsed:        true,
   191  						validated:     true,
   192  					},
   193  				},
   194  			},
   195  		},
   196  		{
   197  			name: "default shared key in default context for both sign and verify",
   198  			config: `
   199                  crypto key token name "foobar token"
   200                  crypto key token lifetime 1800
   201                  crypto key sign-verify foobar
   202              `,
   203  			want: map[string]interface{}{
   204  				"config_count": 1,
   205  				"configs": []*CryptoKeyConfig{
   206  					{
   207  						ID:            "0",
   208  						Usage:         "sign-verify",
   209  						TokenName:     "foobar token",
   210  						Source:        "config",
   211  						Algorithm:     "hmac",
   212  						Secret:        "foobar",
   213  						TokenLifetime: 1800,
   214  						parsed:        true,
   215  						validated:     true,
   216  					},
   217  				},
   218  			},
   219  		},
   220  		{
   221  			name: "multiple shared keys in default context",
   222  			config: `
   223                  crypto key token name "foobar token"
   224                  crypto key verify foobar
   225                  crypto key abc123 token name foobar_token
   226                  crypto key abc123 verify foobar
   227              `,
   228  			want: map[string]interface{}{
   229  				"config_count": 2,
   230  				"configs": []*CryptoKeyConfig{
   231  					{
   232  						ID:            "0",
   233  						Usage:         "verify",
   234  						TokenName:     "foobar token",
   235  						Source:        "config",
   236  						Algorithm:     "hmac",
   237  						Secret:        "foobar",
   238  						TokenLifetime: 900,
   239  						parsed:        true,
   240  						validated:     true,
   241  					},
   242  					{
   243  						Seq:           1,
   244  						ID:            "abc123",
   245  						Usage:         "verify",
   246  						TokenName:     "foobar_token",
   247  						Source:        "config",
   248  						Algorithm:     "hmac",
   249  						Secret:        "foobar",
   250  						TokenLifetime: 900,
   251  						parsed:        true,
   252  						validated:     true,
   253  					},
   254  				},
   255  			},
   256  		},
   257  		{
   258  			name: "multiple shared keys in with implicit token name config",
   259  			config: `
   260                  crypto key verify foobar
   261                  crypto key abc123 verify foobar
   262              `,
   263  			want: map[string]interface{}{
   264  				"config_count": 2,
   265  				"configs": []*CryptoKeyConfig{
   266  					{
   267  						ID:            "0",
   268  						Usage:         "verify",
   269  						Source:        "config",
   270  						Algorithm:     "hmac",
   271  						Secret:        "foobar",
   272  						TokenName:     "access_token",
   273  						TokenLifetime: 900,
   274  						parsed:        true,
   275  						validated:     true,
   276  					},
   277  					{
   278  						Seq:           1,
   279  						ID:            "abc123",
   280  						Usage:         "verify",
   281  						Source:        "config",
   282  						Algorithm:     "hmac",
   283  						Secret:        "foobar",
   284  						TokenName:     "access_token",
   285  						TokenLifetime: 900,
   286  						parsed:        true,
   287  						validated:     true,
   288  					},
   289  				},
   290  			},
   291  		},
   292  		{
   293  			name: "multiple shared keys in with explicit default token name config",
   294  			config: `
   295                  crypto default token name jwt_token
   296                  crypto key verify foobar
   297                  crypto key abc123 verify foobar
   298                  crypto key abc123 token name foobar_token
   299              `,
   300  			want: map[string]interface{}{
   301  				"config_count": 2,
   302  				"configs": []*CryptoKeyConfig{
   303  					{
   304  						ID:            "0",
   305  						Usage:         "verify",
   306  						Source:        "config",
   307  						Algorithm:     "hmac",
   308  						Secret:        "foobar",
   309  						TokenName:     "jwt_token",
   310  						TokenLifetime: 900,
   311  						parsed:        true,
   312  						validated:     true,
   313  					},
   314  					{
   315  						Seq:           1,
   316  						ID:            "abc123",
   317  						Usage:         "verify",
   318  						TokenName:     "foobar_token",
   319  						Source:        "config",
   320  						Algorithm:     "hmac",
   321  						Secret:        "foobar",
   322  						TokenLifetime: 900,
   323  						parsed:        true,
   324  						validated:     true,
   325  					},
   326  				},
   327  			},
   328  		},
   329  		{
   330  			name: "single default shared key",
   331  			config: `
   332                  crypto key verify foobar
   333              `,
   334  			want: map[string]interface{}{
   335  				"config_count": 1,
   336  				"configs": []*CryptoKeyConfig{
   337  					{
   338  						ID:            "0",
   339  						Usage:         "verify",
   340  						Source:        "config",
   341  						Algorithm:     "hmac",
   342  						Secret:        "foobar",
   343  						TokenName:     "access_token",
   344  						TokenLifetime: 900,
   345  						parsed:        true,
   346  						validated:     true,
   347  					},
   348  				},
   349  			},
   350  		},
   351  		{
   352  			name: "multiple default shared keys",
   353  			config: `
   354                  crypto key sign foobar
   355                  crypto key sign barfoo
   356              `,
   357  			shouldErr: true,
   358  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   359  				`crypto key sign barfoo`,
   360  				`duplicate key id`,
   361  			),
   362  		},
   363  		{
   364  			name: "load key from file path",
   365  			config: `
   366                  crypto key k9738a405e99 verify from file /path/to/file
   367              `,
   368  			want: map[string]interface{}{
   369  				"config_count": 1,
   370  				"configs": []*CryptoKeyConfig{
   371  					{
   372  						ID:            "k9738a405e99",
   373  						Usage:         "verify",
   374  						Source:        "config",
   375  						FilePath:      "/path/to/file",
   376  						TokenName:     "access_token",
   377  						TokenLifetime: 900,
   378  						parsed:        true,
   379  						validated:     true,
   380  					},
   381  				},
   382  			},
   383  		},
   384  		{
   385  			name: "load private-public key pair from separate file paths",
   386  			config: `
   387                  crypto key k9738a405e99 sign from file ./../../testdata/rskeys/test_2_pri.pem
   388                  crypto key k9738a405e99 verify from file ./../../testdata/rskeys/test_2_pub.pem
   389              `,
   390  			want: map[string]interface{}{
   391  				"config_count": 2,
   392  				"configs": []*CryptoKeyConfig{
   393  					{
   394  						ID:            "k9738a405e99",
   395  						Usage:         "sign",
   396  						TokenName:     "access_token",
   397  						Source:        "config",
   398  						FilePath:      "./../../testdata/rskeys/test_2_pri.pem",
   399  						TokenLifetime: 900,
   400  						parsed:        true,
   401  						validated:     true,
   402  					},
   403  					{
   404  						Seq:           1,
   405  						ID:            "k9738a405e99",
   406  						Usage:         "verify",
   407  						TokenName:     "access_token",
   408  						Source:        "config",
   409  						FilePath:      "./../../testdata/rskeys/test_2_pub.pem",
   410  						TokenLifetime: 900,
   411  						parsed:        true,
   412  						validated:     true,
   413  					},
   414  				},
   415  			},
   416  		},
   417  		{
   418  			name: "load keys from directory path",
   419  			config: `
   420                  crypto key k9738a405e99 verify from directory /path/to/dir
   421              `,
   422  			want: map[string]interface{}{
   423  				"config_count": 1,
   424  				"configs": []*CryptoKeyConfig{
   425  					{
   426  						ID:            "k9738a405e99",
   427  						Usage:         "verify",
   428  						Source:        "config",
   429  						DirPath:       "/path/to/dir",
   430  						TokenName:     "access_token",
   431  						TokenLifetime: 900,
   432  						parsed:        true,
   433  						validated:     true,
   434  					},
   435  				},
   436  			},
   437  		},
   438  		{
   439  			name: "shared secret embedded in environment variable",
   440  			config: `
   441                  crypto key cb315f43c868 verify from env JWT_SHARED_SECRET
   442              `,
   443  			env: map[string]string{
   444  				"JWT_SHARED_SECRET": "foobar",
   445  			},
   446  			want: map[string]interface{}{
   447  				"config_count": 1,
   448  				"configs": []*CryptoKeyConfig{
   449  					{
   450  						ID:            "cb315f43c868",
   451  						Usage:         "verify",
   452  						Source:        "env",
   453  						EnvVarName:    "JWT_SHARED_SECRET",
   454  						EnvVarValue:   "foobar",
   455  						EnvVarType:    "key",
   456  						TokenName:     "access_token",
   457  						TokenLifetime: 900,
   458  						parsed:        true,
   459  						validated:     true,
   460  					},
   461  				},
   462  			},
   463  		},
   464  		{
   465  			name: "empty env variable value",
   466  			config: `
   467                  crypto key cb315f43c868 verify from env JWT_SHARED_SECRET
   468              `,
   469  			env: map[string]string{
   470  				"JWT_SHARED_SECRET": "     ",
   471  			},
   472  			shouldErr: true,
   473  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   474  				"crypto key cb315f43c868 verify from env JWT_SHARED_SECRET",
   475  				errors.ErrCryptoKeyConfigEmptyEnvVar.WithArgs("JWT_SHARED_SECRET"),
   476  			),
   477  		},
   478  		{
   479  			name: "load key from the value in JWT_SECRET_KEY environment variable",
   480  			config: `
   481                  crypto key cb315f43c868 verify from env JWT_SECRET_KEY as key
   482              `,
   483  			env: map[string]string{
   484  				"JWT_SECRET_KEY": "----BEGIN RSA ...",
   485  			},
   486  			want: map[string]interface{}{
   487  				"config_count": 1,
   488  				"configs": []*CryptoKeyConfig{
   489  					{
   490  						ID:            "cb315f43c868",
   491  						Usage:         "verify",
   492  						Source:        "env",
   493  						EnvVarName:    "JWT_SECRET_KEY",
   494  						EnvVarValue:   "----BEGIN RSA ...",
   495  						EnvVarType:    "key",
   496  						TokenName:     "access_token",
   497  						TokenLifetime: 900,
   498  						parsed:        true,
   499  						validated:     true,
   500  					},
   501  				},
   502  			},
   503  		},
   504  		{
   505  			name: "load key from the file named in JWT_SECRET_FILE environment variable",
   506  			config: `
   507                  crypto key cb315f43c868 verify from env JWT_SECRET_FILE as file
   508              `,
   509  			env: map[string]string{
   510  				"JWT_SECRET_FILE": "/path/to/file",
   511  			},
   512  			want: map[string]interface{}{
   513  				"config_count": 1,
   514  				"configs": []*CryptoKeyConfig{
   515  					{
   516  						ID:            "cb315f43c868",
   517  						Usage:         "verify",
   518  						Source:        "env",
   519  						EnvVarName:    "JWT_SECRET_FILE",
   520  						EnvVarValue:   "/path/to/file",
   521  						EnvVarType:    "file",
   522  						TokenName:     "access_token",
   523  						TokenLifetime: 900,
   524  						parsed:        true,
   525  						validated:     true,
   526  					},
   527  				},
   528  			},
   529  		},
   530  		{
   531  			name: "load keys from the files in the directory named in JWT_SECRET_DIR environment variable",
   532  			config: `
   533                  crypto key cb315f43c868 verify from env JWT_SECRET_DIR as directory
   534              `,
   535  			env: map[string]string{
   536  				"JWT_SECRET_DIR": "/path/to/dir",
   537  			},
   538  			want: map[string]interface{}{
   539  				"config_count": 1,
   540  				"configs": []*CryptoKeyConfig{
   541  					{
   542  						ID:            "cb315f43c868",
   543  						Usage:         "verify",
   544  						Source:        "env",
   545  						EnvVarName:    "JWT_SECRET_DIR",
   546  						EnvVarValue:   "/path/to/dir",
   547  						EnvVarType:    "directory",
   548  						TokenName:     "access_token",
   549  						TokenLifetime: 900,
   550  						parsed:        true,
   551  						validated:     true,
   552  					},
   553  				},
   554  			},
   555  		},
   556  		{
   557  			name: "config entry is too short",
   558  			config: `
   559                  crypto key
   560              `,
   561  			shouldErr: true,
   562  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   563  				"crypto key", "entry is too short",
   564  			),
   565  		},
   566  		{
   567  			name: "config entry without closing quote",
   568  			config: `
   569                  crypto key "foo
   570              `,
   571  			shouldErr: true,
   572  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   573  				`crypto key "foo`,
   574  				`parse error on line 1, column 16: extraneous or missing " in quoted-field`,
   575  			),
   576  		},
   577  		{
   578  			name: "config entry with invalid default token setting",
   579  			config: `
   580                  crypto default token foo bar
   581              `,
   582  			shouldErr: true,
   583  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   584  				`crypto default token foo bar`,
   585  				`unknown default token setting`,
   586  			),
   587  		},
   588  		{
   589  			name: "config entry with too short default token setting",
   590  			config: `
   591                  crypto default token lifetime
   592              `,
   593  			shouldErr: true,
   594  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   595  				`crypto default token lifetime`,
   596  				`default token setting too short`,
   597  			),
   598  		},
   599  		{
   600  			name: "config entry with invalid default token lifetime",
   601  			config: `
   602                  crypto default token lifetime abc123
   603              `,
   604  			shouldErr: true,
   605  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   606  				`crypto default token lifetime abc123`,
   607  				`strconv.Atoi: parsing "abc123": invalid syntax`,
   608  			),
   609  		},
   610  		{
   611  			name: "config entry with unknown default setting",
   612  			config: `
   613                  crypto default foo bar foobar
   614              `,
   615  			shouldErr: true,
   616  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   617  				`crypto default foo bar foobar`,
   618  				`unknown default setting`,
   619  			),
   620  		},
   621  		{
   622  			name: "invalid config entry",
   623  			config: `
   624                  crypto foo bar foo bar
   625              `,
   626  			shouldErr: true,
   627  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   628  				`crypto foo bar foo bar`,
   629  				`bad syntax`,
   630  			),
   631  		},
   632  		{
   633  			name: "bad key token syntax",
   634  			config: `
   635  			    crypto key 123 verify foobar
   636                  crypto key 123 token foo
   637              `,
   638  			shouldErr: true,
   639  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   640  				`crypto key 123 token foo`,
   641  				`token must be followed by its attributes`,
   642  			),
   643  		},
   644  		{
   645  			name: "reserved keyword must not be last",
   646  			config: `
   647                  crypto key 123 verify foobar
   648                  crypto key 123 token
   649              `,
   650  			shouldErr: true,
   651  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   652  				`crypto key 123 token`,
   653  				`reserved keyword must not be last`,
   654  			),
   655  		},
   656  		{
   657  			name: "key with invalid token lifetime",
   658  			config: `
   659                  crypto key 123 verify foobar
   660                  crypto key 123 token lifetime abc123
   661              `,
   662  			shouldErr: true,
   663  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   664  				`crypto key 123 token lifetime abc123`,
   665  				`strconv.Atoi: parsing "abc123": invalid syntax`,
   666  			),
   667  		},
   668  		{
   669  			name: "key with unknown key token setting",
   670  			config: `
   671                  crypto key 123 verify foobar
   672                  crypto key 123 token foo bar
   673              `,
   674  			shouldErr: true,
   675  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   676  				`crypto key 123 token foo bar`,
   677  				`unknown key token setting`,
   678  			),
   679  		},
   680  		{
   681  			name: "key with usage with bad syntax",
   682  			config: `
   683                  crypto key 123 verify foo bar 
   684              `,
   685  			shouldErr: true,
   686  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   687  				`crypto key 123 verify foo bar`,
   688  				`bad syntax`,
   689  			),
   690  		},
   691  		{
   692  			name: "bad syntax",
   693  			config: `
   694                  crypto key 123 verify foo bar
   695              `,
   696  			shouldErr: true,
   697  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   698  				`crypto key 123 verify foo bar`,
   699  				`bad syntax`,
   700  			),
   701  		},
   702  		{
   703  			name: "invalid from config",
   704  			config: `
   705                  crypto key 123 verify from foo /path/to/file
   706  		    `,
   707  			shouldErr: true,
   708  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   709  				`crypto key 123 verify from foo /path/to/file`,
   710  				`bad syntax`,
   711  			),
   712  		},
   713  		{
   714  			name: "invalid from env",
   715  			config: `
   716                  crypto key 123 verify from env JWT_SECRET_FILE as foo
   717              `,
   718  			shouldErr: true,
   719  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   720  				`crypto key 123 verify from env JWT_SECRET_FILE as foo`,
   721  				`bad syntax`,
   722  			),
   723  		},
   724  		{
   725  			name: "invalid from env as",
   726  			config: `
   727                  crypto key 123 verify from env JWT_SECRET_FILE foo bar
   728              `,
   729  			shouldErr: true,
   730  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   731  				`crypto key 123 verify from env JWT_SECRET_FILE foo bar`,
   732  				`bad syntax`,
   733  			),
   734  		},
   735  		{
   736  			name: "invalid from env as file and empty env var",
   737  			config: `
   738                  crypto key 123 verify from env JWT_SECRET_FILE as file
   739              `,
   740  			shouldErr: true,
   741  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   742  				`crypto key 123 verify from env JWT_SECRET_FILE as file`,
   743  				`environment variable JWT_SECRET_FILE has empty value`,
   744  			),
   745  		},
   746  		{
   747  			name: "invalid from env as file and too long",
   748  			config: `
   749                  crypto key 123 verify from env JWT_SECRET_FILE as file foo
   750              `,
   751  			shouldErr: true,
   752  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   753  				`crypto key 123 verify from env JWT_SECRET_FILE as file foo`,
   754  				`bad syntax`,
   755  			),
   756  		},
   757  		{
   758  			name: "invalid key argument",
   759  			config: `
   760                  crypto key 123 foo foo foo foo foo
   761              `,
   762  			shouldErr: true,
   763  			err: errors.ErrCryptoKeyConfigEntryInvalid.WithArgs(
   764  				`crypto key 123 foo foo foo foo foo`,
   765  				`invalid argument`,
   766  			),
   767  		},
   768  		{
   769  			name: "without key configs",
   770  			config: `
   771                  crypto default token name foo
   772              `,
   773  			shouldErr: true,
   774  			err:       errors.ErrCryptoKeyConfigNoConfigFound,
   775  		},
   776  		{
   777  			name: "with validate error",
   778  			config: `
   779                  crypto key 123 token name foo
   780              `,
   781  			shouldErr: true,
   782  			err:       errors.ErrCryptoKeyConfigKeyInvalid.WithArgs(0, "key usage is not set"),
   783  		},
   784  		{
   785  			name: "load mix static and private keys",
   786  			config: `
   787                  crypto key token name usertoken
   788                  crypto key verify foobar
   789                  crypto key k9738a405e99 verify from file ./../../testdata/rskeys/test_2_pub.pem
   790              `,
   791  			want: map[string]interface{}{
   792  				"config_count": 2,
   793  				"configs": []*CryptoKeyConfig{
   794  					{
   795  						ID:            "0",
   796  						Usage:         "verify",
   797  						TokenName:     "usertoken",
   798  						Source:        "config",
   799  						Algorithm:     "hmac",
   800  						TokenLifetime: 900,
   801  						Secret:        "foobar",
   802  						parsed:        true,
   803  						validated:     true,
   804  					},
   805  					{
   806  						ID:            "k9738a405e99",
   807  						Seq:           1,
   808  						Usage:         "verify",
   809  						TokenName:     "access_token",
   810  						Source:        "config",
   811  						FilePath:      "./../../testdata/rskeys/test_2_pub.pem",
   812  						TokenLifetime: 900,
   813  						parsed:        true,
   814  						validated:     true,
   815  					},
   816  				},
   817  			},
   818  		},
   819  		{
   820  			name: "load mix static and private keys with default token name",
   821  			config: `
   822  				crypto default token name usertoken
   823                  crypto key verify foobar
   824                  crypto key k9738a405e99 verify from file ./../../testdata/rskeys/test_2_pub.pem
   825              `,
   826  			want: map[string]interface{}{
   827  				"config_count": 2,
   828  				"configs": []*CryptoKeyConfig{
   829  					{
   830  						ID:            "0",
   831  						Usage:         "verify",
   832  						TokenName:     "usertoken",
   833  						Source:        "config",
   834  						Algorithm:     "hmac",
   835  						TokenLifetime: 900,
   836  						Secret:        "foobar",
   837  						parsed:        true,
   838  						validated:     true,
   839  					},
   840  					{
   841  						ID:            "k9738a405e99",
   842  						Seq:           1,
   843  						Usage:         "verify",
   844  						TokenName:     "usertoken",
   845  						Source:        "config",
   846  						FilePath:      "./../../testdata/rskeys/test_2_pub.pem",
   847  						TokenLifetime: 900,
   848  						parsed:        true,
   849  						validated:     true,
   850  					},
   851  				},
   852  			},
   853  		},
   854  	}
   855  	for _, tc := range testcases {
   856  		t.Run(tc.name, func(t *testing.T) {
   857  			msgs := []string{fmt.Sprintf("test name: %s", tc.name)}
   858  			msgs = append(msgs, fmt.Sprintf("config: %s", tc.config))
   859  			for k, v := range tc.env {
   860  				msgs = append(msgs, fmt.Sprintf("env: %s = %s", k, v))
   861  				os.Setenv(k, v)
   862  				defer os.Unsetenv(k)
   863  			}
   864  			configs, err := ParseCryptoKeyConfigs(tc.config)
   865  			if tests.EvalErrWithLog(t, err, nil, tc.shouldErr, tc.err, msgs) {
   866  				return
   867  			}
   868  			got := make(map[string]interface{})
   869  			got["config_count"] = len(configs)
   870  			got["configs"] = configs
   871  
   872  			for i, c := range configs {
   873  				msgs = append(msgs, fmt.Sprintf("config %d: %s", i, c.ToString()))
   874  			}
   875  
   876  			if diff := cmp.Diff(tc.want, got, cmp.AllowUnexported(CryptoKeyConfig{})); diff != "" {
   877  				tests.WriteLog(t, msgs)
   878  				t.Fatalf("output mismatch (-want +got):\n%s", diff)
   879  			}
   880  		})
   881  	}
   882  }