Merge pull request #8704 from hashicorp/custom_vault_azure
Custom vault azure
This commit is contained in:
commit
dfefe7e8d9
|
@ -231,8 +231,16 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string)
|
||||
steps = []multistep.Step{
|
||||
NewStepCreateResourceGroup(azureClient, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, &b.config, GetKeyVaultDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, &b.config, keyVaultDeploymentName, GetKeyVaultDeployment),
|
||||
}
|
||||
if b.config.BuildKeyVaultName == "" {
|
||||
steps = append(steps,
|
||||
NewStepValidateTemplate(azureClient, ui, &b.config, GetKeyVaultDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, &b.config, keyVaultDeploymentName, GetKeyVaultDeployment),
|
||||
)
|
||||
} else {
|
||||
steps = append(steps, NewStepCertificateInKeyVault(&azureClient.VaultClient, ui, &b.config))
|
||||
}
|
||||
steps = append(steps,
|
||||
NewStepGetCertificate(azureClient, ui),
|
||||
NewStepSetCertificate(&b.config, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, &b.config, GetVirtualMachineDeployment),
|
||||
|
@ -261,7 +269,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
NewStepDeleteResourceGroup(azureClient, ui),
|
||||
NewStepDeleteOSDisk(azureClient, ui),
|
||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
|
||||
}
|
||||
|
@ -396,6 +404,13 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
|
|||
}
|
||||
|
||||
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
|
||||
stateBag.Put(constants.ArmIsExistingKeyVault, false)
|
||||
if b.config.BuildKeyVaultName != "" {
|
||||
stateBag.Put(constants.ArmKeyVaultName, b.config.BuildKeyVaultName)
|
||||
b.config.tmpKeyVaultName = b.config.BuildKeyVaultName
|
||||
stateBag.Put(constants.ArmIsExistingKeyVault, true)
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmNicName, b.config.tmpNicName)
|
||||
stateBag.Put(constants.ArmPublicIPAddressName, b.config.tmpPublicIPAddressName)
|
||||
stateBag.Put(constants.ArmResourceGroupName, b.config.BuildResourceGroupName)
|
||||
|
|
|
@ -253,7 +253,10 @@ type Config struct {
|
|||
// group is deleted at the end of the build.
|
||||
TempResourceGroupName string `mapstructure:"temp_resource_group_name"`
|
||||
// Specify an existing resource group to run the build in.
|
||||
BuildResourceGroupName string `mapstructure:"build_resource_group_name"`
|
||||
BuildResourceGroupName string `mapstructure:"build_resource_group_name"`
|
||||
// Specify an existing key vault to use for uploading certificates to the
|
||||
// instance to connect.
|
||||
BuildKeyVaultName string `mapstructure:"build_key_vault_name"`
|
||||
storageAccountBlobEndpoint string
|
||||
// This value allows you to
|
||||
// set a virtual_network_name and obtain a public IP. If this value is not
|
||||
|
|
|
@ -53,6 +53,7 @@ type FlatConfig struct {
|
|||
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name"`
|
||||
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name"`
|
||||
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name"`
|
||||
BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name"`
|
||||
PrivateVirtualNetworkWithPublicIp *bool `mapstructure:"private_virtual_network_with_public_ip" required:"false" cty:"private_virtual_network_with_public_ip"`
|
||||
VirtualNetworkName *string `mapstructure:"virtual_network_name" required:"false" cty:"virtual_network_name"`
|
||||
VirtualNetworkSubnetName *string `mapstructure:"virtual_network_subnet_name" required:"false" cty:"virtual_network_subnet_name"`
|
||||
|
@ -166,6 +167,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
|
||||
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
|
||||
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
|
||||
"build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false},
|
||||
"private_virtual_network_with_public_ip": &hcldec.AttrSpec{Name: "private_virtual_network_with_public_ip", Type: cty.Bool, Required: false},
|
||||
"virtual_network_name": &hcldec.AttrSpec{Name: "virtual_network_name", Type: cty.String, Required: false},
|
||||
"virtual_network_subnet_name": &hcldec.AttrSpec{Name: "virtual_network_subnet_name", Type: cty.String, Required: false},
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepCertificateInKeyVault struct {
|
||||
config *Config
|
||||
client common.AZVaultClientIface
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepCertificateInKeyVault(cli common.AZVaultClientIface, ui packer.Ui, config *Config) *StepCertificateInKeyVault {
|
||||
var step = &StepCertificateInKeyVault{
|
||||
client: cli,
|
||||
config: config,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCertificateInKeyVault) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Setting the certificate in the KeyVault...")
|
||||
var keyVaultName = state.Get(constants.ArmKeyVaultName).(string)
|
||||
|
||||
err := s.client.SetSecret(keyVaultName, DefaultSecretName, s.config.winrmCertificate)
|
||||
if err != nil {
|
||||
s.error(fmt.Errorf("Error setting winrm cert in custom keyvault: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepCertificateInKeyVault) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
azcommon "github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestNewStepCertificateInKeyVault(t *testing.T) {
|
||||
cli := azcommon.MockAZVaultClient{}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put(constants.ArmKeyVaultName, "testKeyVaultName")
|
||||
|
||||
config := &Config{
|
||||
winrmCertificate: "testCertificateString",
|
||||
}
|
||||
|
||||
certKVStep := NewStepCertificateInKeyVault(&cli, ui, config)
|
||||
stepAction := certKVStep.Run(context.TODO(), state)
|
||||
|
||||
if stepAction == multistep.ActionHalt {
|
||||
t.Fatalf("step should have succeeded.")
|
||||
}
|
||||
if !cli.SetSecretCalled {
|
||||
t.Fatalf("Step should have called SetSecret on Azure client.")
|
||||
}
|
||||
if cli.SetSecretCert != "testCertificateString" {
|
||||
t.Fatalf("Step should have read cert from winRMCertificate field on config.")
|
||||
}
|
||||
if cli.SetSecretVaultName != "testKeyVaultName" {
|
||||
t.Fatalf("step should have read keyvault name from state.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStepCertificateInKeyVault_error(t *testing.T) {
|
||||
// Tell mock to return an error
|
||||
cli := azcommon.MockAZVaultClient{}
|
||||
cli.IsError = true
|
||||
|
||||
ui := &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put(constants.ArmKeyVaultName, "testKeyVaultName")
|
||||
|
||||
config := &Config{
|
||||
winrmCertificate: "testCertificateString",
|
||||
}
|
||||
|
||||
certKVStep := NewStepCertificateInKeyVault(&cli, ui, config)
|
||||
stepAction := certKVStep.Run(context.TODO(), state)
|
||||
|
||||
if stepAction != multistep.ActionHalt {
|
||||
t.Fatalf("step should have failed.")
|
||||
}
|
||||
}
|
|
@ -44,9 +44,12 @@ func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state
|
|||
}
|
||||
|
||||
if keyVaultDeploymentName, ok := state.GetOk(constants.ArmKeyVaultDeploymentName); ok {
|
||||
err = s.deleteDeploymentResources(ctx, keyVaultDeploymentName.(string), resourceGroupName)
|
||||
if err != nil {
|
||||
return err
|
||||
// Only delete if custom keyvault was not provided.
|
||||
if exists := state.Get(constants.ArmIsExistingKeyVault).(bool); exists {
|
||||
err = s.deleteDeploymentResources(ctx, keyVaultDeploymentName.(string), resourceGroupName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ const (
|
|||
ArmTags string = "arm.Tags"
|
||||
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
|
||||
ArmIsExistingResourceGroup string = "arm.IsExistingResourceGroup"
|
||||
ArmIsExistingKeyVault string = "arm.IsExistingKeyVault"
|
||||
|
||||
ArmIsManagedImage string = "arm.IsManagedImage"
|
||||
ArmManagedImageResourceGroupName string = "arm.ManagedImageResourceGroupName"
|
||||
|
|
|
@ -16,6 +16,15 @@ const (
|
|||
AzureVaultApiVersion = "2016-10-01"
|
||||
)
|
||||
|
||||
// Enables us to test steps that access this cli
|
||||
type AZVaultClientIface interface {
|
||||
GetSecret(string, string) (*Secret, error)
|
||||
SetSecret(string, string, string) error
|
||||
DeletePreparer(string, string) (*http.Request, error)
|
||||
DeleteResponder(*http.Response) (autorest.Response, error)
|
||||
DeleteSender(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type VaultClient struct {
|
||||
autorest.Client
|
||||
keyVaultEndpoint url.URL
|
||||
|
@ -54,7 +63,8 @@ func (client *VaultClient) GetSecret(vaultName, secretName string) (*Secret, err
|
|||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(client.getVaultUrl(vaultName)),
|
||||
autorest.WithPathParameters("/secrets/{secret-name}", p),
|
||||
autorest.WithQueryParameters(q))
|
||||
autorest.WithQueryParameters(q),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -86,6 +96,47 @@ func (client *VaultClient) GetSecret(vaultName, secretName string) (*Secret, err
|
|||
return &secret, nil
|
||||
}
|
||||
|
||||
func (client *VaultClient) SetSecret(vaultName, secretName string, secretValue string) error {
|
||||
p := map[string]interface{}{
|
||||
"secret-name": autorest.Encode("path", secretName),
|
||||
}
|
||||
q := map[string]interface{}{
|
||||
"api-version": AzureVaultApiVersion,
|
||||
}
|
||||
|
||||
jsonBody := fmt.Sprintf(`{"value": "%s"}`, secretValue)
|
||||
|
||||
req, err := autorest.Prepare(
|
||||
&http.Request{},
|
||||
autorest.AsPut(),
|
||||
autorest.AsContentType("application/json; charset=utf-8"),
|
||||
autorest.WithBaseURL(client.getVaultUrl(vaultName)),
|
||||
autorest.WithPathParameters("/secrets/{secret-name}", p),
|
||||
autorest.WithQueryParameters(q),
|
||||
autorest.WithString(jsonBody),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf(
|
||||
"Failed to set secret to %s/%s, HTTP status code=%d (%s)",
|
||||
vaultName,
|
||||
secretName,
|
||||
resp.StatusCode,
|
||||
http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the specified Azure key vault.
|
||||
//
|
||||
// resourceGroupName is the name of the Resource Group to which the vault belongs. vaultName is the name of the vault
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
type MockAZVaultClient struct {
|
||||
GetSecretCalled bool
|
||||
SetSecretCalled bool
|
||||
SetSecretVaultName string
|
||||
SetSecretSecretName string
|
||||
SetSecretCert string
|
||||
DeleteResponderCalled bool
|
||||
DeletePreparerCalled bool
|
||||
DeleteSenderCalled bool
|
||||
|
||||
IsError bool
|
||||
}
|
||||
|
||||
func (m *MockAZVaultClient) GetSecret(vaultName, secretName string) (*Secret, error) {
|
||||
m.GetSecretCalled = true
|
||||
var secret Secret
|
||||
return &secret, nil
|
||||
}
|
||||
|
||||
func (m *MockAZVaultClient) SetSecret(vaultName, secretName string, secretValue string) error {
|
||||
m.SetSecretCalled = true
|
||||
m.SetSecretVaultName = vaultName
|
||||
m.SetSecretSecretName = secretName
|
||||
m.SetSecretCert = secretValue
|
||||
|
||||
if m.IsError {
|
||||
return fmt.Errorf("generic error!!")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockAZVaultClient) DeletePreparer(resourceGroupName string, vaultName string) (*http.Request, error) {
|
||||
m.DeletePreparerCalled = true
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockAZVaultClient) DeleteResponder(resp *http.Response) (autorest.Response, error) {
|
||||
m.DeleteResponderCalled = true
|
||||
var result autorest.Response
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *MockAZVaultClient) DeleteSender(req *http.Request) (*http.Response, error) {
|
||||
m.DeleteSenderCalled = true
|
||||
return nil, nil
|
||||
}
|
|
@ -132,6 +132,9 @@
|
|||
|
||||
- `build_resource_group_name` (string) - Specify an existing resource group to run the build in.
|
||||
|
||||
- `build_key_vault_name` (string) - Specify an existing key vault to use for uploading certificates to the
|
||||
instance to connect.
|
||||
|
||||
- `private_virtual_network_with_public_ip` (bool) - This value allows you to
|
||||
set a virtual_network_name and obtain a public IP. If this value is not
|
||||
set and virtual_network_name is defined Packer is only allowed to be
|
||||
|
|
Loading…
Reference in New Issue