github.com/palantir/witchcraft-go-server/v2@v2.76.0/integration/config_test.go (about)

     1  // Copyright (c) 2019 Palantir Technologies. All rights reserved.
     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 integration
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  
    26  	"github.com/palantir/witchcraft-go-server/v2/config"
    27  	"github.com/palantir/witchcraft-go-server/v2/witchcraft"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestEncryptedConfig(t *testing.T) {
    33  	const (
    34  		decryptedValue          = "hello world"
    35  		encryptionKey           = "AES:T6H7a4WvQS9ITcNIihyUIj30K4SIrD6dB39ENJQ7oAo="
    36  		encryptedValue          = "${enc:eyJ0eXBlIjoiQUVTIiwibW9kZSI6IkdDTSIsImNpcGhlcnRleHQiOiJqcGl0bThQRStRekd2YlE9IiwiaXYiOiJrTHlBOEZBNzFnTDVpdkswIiwidGFnIjoicmxpcXY3amYwbWVnaGU1N0pyQ3ZzZz09In0=}"
    37  		decryptedMultilineValue = `-----BEGIN CERTIFICATE-----
    38  cert
    39  -----END CERTIFICATE-----
    40  `
    41  		encryptedMultilineValue = "${enc:eyJ0eXBlIjoiQUVTIiwibW9kZSI6IkdDTSIsImNpcGhlcnRleHQiOiJuTXNWOEV1L1JpWGN1TGFDbVNSRVVQUG1NbFpab0paMEN6QlNuL3BWUWwzWUhkWXlVSEZYaGY5c3N5NlFTbzVXYnp5bTlaTCtmQytxQTZNPSIsIml2IjoiRzNtSHp1SmRVU0NncXRLcyIsInRhZyI6IlI2UWZPb2UvS1hiSFZiTmZDaWNkVlE9PSJ9}"
    42  	)
    43  
    44  	type message struct {
    45  		config.Runtime `yaml:",inline"`
    46  		Message        string `yaml:"message"`
    47  	}
    48  
    49  	type messageInstall struct {
    50  		config.Install `yaml:",inline"`
    51  		Message        string `yaml:"message"`
    52  	}
    53  
    54  	for _, test := range []struct {
    55  		Name          string
    56  		ECVKeyContent string
    57  		InstallConfig string
    58  		RuntimeConfig string
    59  		// Use InitFn to verify config
    60  		Verify    func(info witchcraft.InitInfo) error
    61  		VerifyLog func(t *testing.T, logOutput []byte)
    62  	}{
    63  		{
    64  			Name:          "init function sees decrypted config",
    65  			ECVKeyContent: encryptionKey,
    66  			InstallConfig: fmt.Sprintf("message: %s\nuse-console-log: true\n", encryptedValue),
    67  			RuntimeConfig: fmt.Sprintf("message: %s\nlogging:\n  level: warn\n", encryptedValue),
    68  			Verify: func(info witchcraft.InitInfo) error {
    69  				if msg := info.InstallConfig.(*messageInstall).Message; msg != decryptedValue {
    70  					return fmt.Errorf("expected %q got %q", decryptedValue, msg)
    71  				}
    72  				if msg := info.RuntimeConfig.Current().(*message).Message; msg != decryptedValue {
    73  					return fmt.Errorf("expected %q got %q", decryptedValue, msg)
    74  				}
    75  				return nil
    76  			},
    77  			VerifyLog: func(t *testing.T, logOutput []byte) {
    78  				assert.Equal(t, []string{"abort startup"}, getLogFileMessages(t, logOutput))
    79  			},
    80  		},
    81  		{
    82  			Name:          "encrypted config can be concatenated with other strings",
    83  			ECVKeyContent: encryptionKey,
    84  			InstallConfig: fmt.Sprintf("message: prefix%ssuffix\nuse-console-log: true\n", encryptedValue),
    85  			RuntimeConfig: fmt.Sprintf("message: prefix%ssuffix%s\nlogging:\n  level: warn\n", encryptedValue, encryptedMultilineValue),
    86  			Verify: func(info witchcraft.InitInfo) error {
    87  				expectedInstallValue := fmt.Sprintf("prefix%ssuffix", decryptedValue)
    88  				if msg := info.InstallConfig.(*messageInstall).Message; msg != expectedInstallValue {
    89  					return fmt.Errorf("expected %q got %q", expectedInstallValue, msg)
    90  				}
    91  				expectedRuntimeValue := fmt.Sprintf("prefix%ssuffix%s", decryptedValue, decryptedMultilineValue)
    92  				if msg := info.RuntimeConfig.Current().(*message).Message; msg != expectedRuntimeValue {
    93  					return fmt.Errorf("expected %q got %q", expectedRuntimeValue, msg)
    94  				}
    95  				return nil
    96  			},
    97  			VerifyLog: func(t *testing.T, logOutput []byte) {
    98  				assert.Equal(t, []string{"abort startup"}, getLogFileMessages(t, logOutput))
    99  			},
   100  		},
   101  		{
   102  			Name:          "encrypted multi-line value is decrypted properly",
   103  			ECVKeyContent: encryptionKey,
   104  			InstallConfig: fmt.Sprintf("message: %s\nuse-console-log: true\n", encryptedMultilineValue),
   105  			RuntimeConfig: fmt.Sprintf("message: %s\nlogging:\n  level: warn\n", encryptedMultilineValue),
   106  			Verify: func(info witchcraft.InitInfo) error {
   107  				if msg := info.InstallConfig.(*messageInstall).Message; msg != decryptedMultilineValue {
   108  					return fmt.Errorf("expected %q got %q", decryptedValue, msg)
   109  				}
   110  				if msg := info.RuntimeConfig.Current().(*message).Message; msg != decryptedMultilineValue {
   111  					return fmt.Errorf("expected %q got %q", decryptedValue, msg)
   112  				}
   113  				return nil
   114  			},
   115  			VerifyLog: func(t *testing.T, logOutput []byte) {
   116  				assert.Equal(t, []string{"abort startup"}, getLogFileMessages(t, logOutput))
   117  			},
   118  		},
   119  		{
   120  			Name:          "no ecv required if no encrypted config",
   121  			ECVKeyContent: "test_invalid_key_material",
   122  			InstallConfig: "message: hello install\nuse-console-log: true\n",
   123  			RuntimeConfig: "message: hello runtime\nlogging:\n  level: warn\n",
   124  			Verify: func(info witchcraft.InitInfo) error {
   125  				if msg := info.InstallConfig.(*messageInstall).Message; msg != "hello install" {
   126  					return fmt.Errorf("expected %q got %q", "hello install", msg)
   127  				}
   128  				if msg := info.RuntimeConfig.Current().(*message).Message; msg != "hello runtime" {
   129  					return fmt.Errorf("expected %q got %q", "hello runtime", msg)
   130  				}
   131  				return nil
   132  			},
   133  			VerifyLog: func(t *testing.T, logOutput []byte) {
   134  				assert.Equal(t, []string{"abort startup"}, getLogFileMessages(t, logOutput))
   135  			},
   136  		},
   137  		{
   138  			Name:          "warning printed if ecv key fails in runtime",
   139  			ECVKeyContent: "test_invalid_key_material",
   140  			InstallConfig: "message: hello install\nuse-console-log: true\n",
   141  			RuntimeConfig: fmt.Sprintf("message: %s\nlogging:\n  level: warn\n", encryptedValue),
   142  			Verify: func(info witchcraft.InitInfo) error {
   143  				if msg := info.InstallConfig.(*messageInstall).Message; msg != "hello install" {
   144  					return fmt.Errorf("expected %q got %q", "hello install", msg)
   145  				}
   146  				if msg := info.RuntimeConfig.Current().(*message).Message; msg != encryptedValue {
   147  					return fmt.Errorf("expected %q got %q", encryptedValue, msg)
   148  				}
   149  				return nil
   150  			},
   151  			VerifyLog: func(t *testing.T, logOutput []byte) {
   152  				assert.Equal(t, []string{"Failed to decrypt encrypted runtime configuration", "abort startup"}, getLogFileMessages(t, logOutput))
   153  			},
   154  		},
   155  		{
   156  			Name:          "warning printed if encrypted config cannot be decrypted in runtime",
   157  			ECVKeyContent: encryptionKey,
   158  			InstallConfig: "message: hello install\nuse-console-log: true\n",
   159  			RuntimeConfig: fmt.Sprintf("message: %s\nlogging:\n  level: warn\n", "${enc:invalid}"),
   160  			Verify: func(info witchcraft.InitInfo) error {
   161  				if msg := info.InstallConfig.(*messageInstall).Message; msg != "hello install" {
   162  					return fmt.Errorf("expected %q got %q", "hello install", msg)
   163  				}
   164  				if msg := info.RuntimeConfig.Current().(*message).Message; msg != "${enc:invalid}" {
   165  					return fmt.Errorf("expected %q got %q", "${enc:invalid}", msg)
   166  				}
   167  				return nil
   168  			},
   169  			VerifyLog: func(t *testing.T, logOutput []byte) {
   170  				assert.Equal(t, []string{"Failed to decrypt encrypted runtime configuration", "abort startup"}, getLogFileMessages(t, logOutput))
   171  			},
   172  		},
   173  	} {
   174  		t.Run(test.Name, func(t *testing.T) {
   175  			tmpDir, err := ioutil.TempDir("", "TestEncryptedConfig_")
   176  			require.NoError(t, err)
   177  			defer func() {
   178  				_ = os.RemoveAll(tmpDir)
   179  			}()
   180  			ecvKeyFile := filepath.Join(tmpDir, "ecv.key")
   181  			err = ioutil.WriteFile(ecvKeyFile, []byte(test.ECVKeyContent), 0600)
   182  			require.NoError(t, err)
   183  			installFile := filepath.Join(tmpDir, "install.yml")
   184  			err = ioutil.WriteFile(installFile, []byte(test.InstallConfig), 0644)
   185  			require.NoError(t, err)
   186  			runtimeFile := filepath.Join(tmpDir, "runtime.yml")
   187  			err = ioutil.WriteFile(runtimeFile, []byte(test.RuntimeConfig), 0644)
   188  			require.NoError(t, err)
   189  
   190  			logOutputBuffer := &bytes.Buffer{}
   191  			server := witchcraft.NewServer().
   192  				WithECVKeyFromFile(ecvKeyFile).
   193  				WithInstallConfigFromFile(installFile).
   194  				WithInstallConfigType(&messageInstall{}).
   195  				WithRuntimeConfigFromFile(runtimeFile).
   196  				WithRuntimeConfigType(&message{}).
   197  				WithLoggerStdoutWriter(logOutputBuffer).
   198  				WithDisableGoRuntimeMetrics().
   199  				WithSelfSignedCertificate().
   200  				WithInitFunc(func(ctx context.Context, info witchcraft.InitInfo) (cleanup func(), rErr error) {
   201  					if err := test.Verify(info); err != nil {
   202  						return nil, err
   203  					}
   204  					return nil, fmt.Errorf("abort startup")
   205  				})
   206  			// Execute server, expecting error
   207  			err = server.Start()
   208  			require.EqualError(t, err, "abort startup")
   209  
   210  			if test.VerifyLog != nil {
   211  				test.VerifyLog(t, logOutputBuffer.Bytes())
   212  			}
   213  		})
   214  	}
   215  }