Merge pull request #9672 from hashicorp/ca-force-skip-xc

connect/ca: Allow ForceWithoutCrossSigning for all providers
This commit is contained in:
Kyle Havlovitz 2021-03-11 11:49:15 -08:00 committed by GitHub
commit 237b41ac8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 6 deletions

4
.changelog/9672.txt Normal file
View file

@ -0,0 +1,4 @@
```release-note:improvement
cli: added a `-force-without-cross-signing` flag to the `ca set-config` command.
connect/ca: The ForceWithoutCrossSigning field will now work as expected for CA providers that support cross signing.
```

View file

@ -887,12 +887,13 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)
"You can try again with ForceWithoutCrossSigningSet but this may cause " + "You can try again with ForceWithoutCrossSigningSet but this may cause " +
"disruption - see documentation for more.") "disruption - see documentation for more.")
} }
if !canXSign && args.Config.ForceWithoutCrossSigning { if args.Config.ForceWithoutCrossSigning {
c.logger.Warn("current CA doesn't support cross signing but " + c.logger.Warn("ForceWithoutCrossSigning set, CA reconfiguration skipping cross-signing")
"CA reconfiguration forced anyway with ForceWithoutCrossSigning")
} }
if canXSign { // If ForceWithoutCrossSigning wasn't set, attempt to have the old CA generate a
// cross-signed intermediate.
if canXSign && !args.Config.ForceWithoutCrossSigning {
// Have the old provider cross-sign the new root // Have the old provider cross-sign the new root
xcCert, err := oldProvider.CrossSignCA(newRoot) xcCert, err := oldProvider.CrossSignCA(newRoot)
if err != nil { if err != nil {

View file

@ -1410,3 +1410,130 @@ func TestLeader_Consul_BadCAConfigShouldntPreventLeaderEstablishment(t *testing.
require.NotEmpty(t, rootsList.Roots) require.NotEmpty(t, rootsList.Roots)
require.NotNil(t, activeRoot) require.NotNil(t, activeRoot)
} }
func TestLeader_Consul_ForceWithoutCrossSigning(t *testing.T) {
require := require.New(t)
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()
waitForLeaderEstablishment(t, s1)
// Get the current root
rootReq := &structs.DCSpecificRequest{
Datacenter: "dc1",
}
var rootList structs.IndexedCARoots
require.Nil(msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", rootReq, &rootList))
require.Len(rootList.Roots, 1)
oldRoot := rootList.Roots[0]
// Update the provider config to use a new private key, which should
// cause a rotation.
_, newKey, err := connect.GeneratePrivateKey()
require.NoError(err)
newConfig := &structs.CAConfiguration{
Provider: "consul",
Config: map[string]interface{}{
"LeafCertTTL": "500ms",
"PrivateKey": newKey,
"RootCert": "",
"RotationPeriod": "2160h",
"SkipValidate": true,
},
ForceWithoutCrossSigning: true,
}
{
args := &structs.CARequest{
Datacenter: "dc1",
Config: newConfig,
}
var reply interface{}
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply))
}
// Old root should no longer be active.
_, roots, err := s1.fsm.State().CARoots(nil)
require.NoError(err)
require.Len(roots, 2)
for _, r := range roots {
if r.ID == oldRoot.ID {
require.False(r.Active)
} else {
require.True(r.Active)
}
}
}
func TestLeader_Vault_ForceWithoutCrossSigning(t *testing.T) {
ca.SkipIfVaultNotPresent(t)
require := require.New(t)
testVault := ca.NewTestVaultServer(t)
defer testVault.Stop()
_, s1 := testServerWithConfig(t, func(c *Config) {
c.Build = "1.9.1"
c.PrimaryDatacenter = "dc1"
c.CAConfig = &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
},
}
})
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()
waitForLeaderEstablishment(t, s1)
// Get the current root
rootReq := &structs.DCSpecificRequest{
Datacenter: "dc1",
}
var rootList structs.IndexedCARoots
require.Nil(msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", rootReq, &rootList))
require.Len(rootList.Roots, 1)
oldRoot := rootList.Roots[0]
// Update the provider config to use a new PKI path, which should
// cause a rotation.
newConfig := &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root-2/",
"IntermediatePKIPath": "pki-intermediate/",
},
ForceWithoutCrossSigning: true,
}
{
args := &structs.CARequest{
Datacenter: "dc1",
Config: newConfig,
}
var reply interface{}
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply))
}
// Old root should no longer be active.
_, roots, err := s1.fsm.State().CARoots(nil)
require.NoError(err)
require.Len(roots, 2)
for _, r := range roots {
if r.ID == oldRoot.ID {
require.False(r.Active)
} else {
require.True(r.Active)
}
}
}

View file

@ -23,6 +23,14 @@ type CAConfig struct {
// configuration is an error. // configuration is an error.
State map[string]string State map[string]string
// ForceWithoutCrossSigning indicates that the CA reconfiguration should go
// ahead even if the current CA is unable to cross sign certificates. This
// risks temporary connection failures during the rollout as new leafs will be
// rejected by proxies that have not yet observed the new root cert but is the
// only option if a CA that doesn't support cross signing needs to be
// reconfigured or mirated away from.
ForceWithoutCrossSigning bool
CreateIndex uint64 CreateIndex uint64
ModifyIndex uint64 ModifyIndex uint64
} }

View file

@ -24,13 +24,20 @@ type cmd struct {
help string help string
// flags // flags
configFile flags.StringValue configFile flags.StringValue
forceWithoutCrossSigning bool
} }
func (c *cmd) init() { func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError) c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.flags.Var(&c.configFile, "config-file", c.flags.Var(&c.configFile, "config-file",
"The path to the config file to use.") "The path to the config file to use.")
c.flags.BoolVar(&c.forceWithoutCrossSigning, "force-without-cross-signing", false,
"Indicates that the CA reconfiguration should go ahead even if the current "+
"CA is unable to cross sign certificates. This risks temporary connection "+
"failures during the rollout as new leafs will be rejected by proxies that "+
"have not yet observed the new root cert but is the only option if a CA that "+
"doesn't support cross signing needs to be reconfigured or mirated away from.")
c.http = &flags.HTTPFlags{} c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags()) flags.Merge(c.flags, c.http.ClientFlags())
@ -70,6 +77,7 @@ func (c *cmd) Run(args []string) int {
c.UI.Error(fmt.Sprintf("Error parsing config file: %s", err)) c.UI.Error(fmt.Sprintf("Error parsing config file: %s", err))
return 1 return 1
} }
config.ForceWithoutCrossSigning = c.forceWithoutCrossSigning
// Set the new configuration. // Set the new configuration.
if _, err := client.Connect().CASetConfig(&config, nil); err != nil { if _, err := client.Connect().CASetConfig(&config, nil); err != nil {

View file

@ -176,7 +176,7 @@ The table below shows this endpoint's support for
providers, see [Provider Config](/docs/connect/ca). providers, see [Provider Config](/docs/connect/ca).
- `ForceWithoutCrossSigning` `(bool: <optional>)` - Indicates that the CA change - `ForceWithoutCrossSigning` `(bool: <optional>)` - Indicates that the CA change
should be force to complete even if the current CA doesn't support cross should be forced to complete even if the current CA doesn't support cross
signing. Changing root without cross-signing may cause temporary connection signing. Changing root without cross-signing may cause temporary connection
failures until the rollout completes. See [Forced Rotation Without failures until the rollout completes. See [Forced Rotation Without
Cross-Signing](/docs/connect/ca#forced-rotation-without-cross-signing) Cross-Signing](/docs/connect/ca#forced-rotation-without-cross-signing)

View file

@ -82,6 +82,13 @@ Usage: `consul connect ca set-config [options]`
The format of this config file matches the request payload documented in the The format of this config file matches the request payload documented in the
[Update CA Configuration API](/api/connect/ca#update-ca-configuration). [Update CA Configuration API](/api/connect/ca#update-ca-configuration).
- `-force-without-cross-signing` `(bool: <optional>)` - Indicates that the CA change
should be forced to complete even if the current CA doesn't support cross
signing. Changing root without cross-signing may cause temporary connection
failures until the rollout completes. See [Forced Rotation Without
Cross-Signing](/docs/connect/ca#forced-rotation-without-cross-signing)
for more detail.
The output looks like this: The output looks like this:
``` ```