Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rotate certificate upon valid principals change. #2812

Merged
merged 1 commit into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions lib/service/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,37 @@ type rotationStatus struct {
ca services.CertAuthority
}

// checkPrincipals returns a boolean that indicates the host certificate
// needs to be regenerated.
func checkPrincipals(conn *Connector, additionalPrincipals []string, dnsNames []string) bool {
var principalsChanged bool
var dnsNamesChanged bool

// Remove 0.0.0.0 (meaning advertise_ip has not) if it exists in the list of
// principals. The 0.0.0.0 values tells the auth server to "guess" the nodes
// IP. If 0.0.0.0 is not removed, a check is performed if it exists in the
// list of principals in the certificate. Since it never exists in the list
// of principals (auth server will always remove it before issuing a
// certificate) regeneration is always requested.
principalsToCheck := utils.RemoveFromSlice(additionalPrincipals, defaults.AnyAddress)

// If advertise_ip, public_addr, or listen_addr in file configuration were
// updated, the list of principals (SSH) or DNS names (TLS) on the
// certificate need to be updated.
if len(additionalPrincipals) != 0 && !conn.ServerIdentity.HasPrincipals(principalsToCheck) {
principalsChanged = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if all you care about is that the certificates have changed, you can return here, and avoid the extra variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for logging purposes, so we can debug what changed (was it SSH or TLS or both).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha

log.Debugf("Rotation in progress, adding %v to SSH principals %v.",
additionalPrincipals, conn.ServerIdentity.Cert.ValidPrincipals)
}
if len(dnsNames) != 0 && !conn.ServerIdentity.HasDNSNames(dnsNames) {
dnsNamesChanged = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here?

log.Debugf("Rotation in progress, adding %v to x590 DNS names in SAN %v.",
dnsNames, conn.ServerIdentity.XCert.DNSNames)
}

return principalsChanged || dnsNamesChanged
}

// rotate is called to check if rotation should be triggered.
func (process *TeleportProcess) rotate(conn *Connector, localState auth.StateV2, remote services.Rotation) (*rotationStatus, error) {
id := conn.ClientIdentity.ID
Expand All @@ -610,31 +641,13 @@ func (process *TeleportProcess) rotate(conn *Connector, localState auth.StateV2,
return nil, trace.Wrap(err)
}

additionalPrincipals = utils.ReplaceInSlice(
additionalPrincipals,
defaults.AnyAddress,
defaults.Localhost,
)

// If advertise_ip, public_addr, or listen_addr in file configuration were
// updated, the list of principals (SSH) and DNS names (TLS) on the
// certificate need to be updated.
var principalsChanged bool
if len(additionalPrincipals) != 0 && !conn.ServerIdentity.HasPrincipals(additionalPrincipals) {
principalsChanged = true
log.Debugf("Rotation in progress, updating SSH principals from %v to %v.",
conn.ServerIdentity.Cert.ValidPrincipals, additionalPrincipals)
}
var dnsNamesChanged bool
if len(dnsNames) != 0 && !conn.ServerIdentity.HasDNSNames(dnsNames) {
log.Debugf("Rotation in progress, updating x590 DNS names in SAN from %v to %v.",
conn.ServerIdentity.XCert.DNSNames, dnsNames)
dnsNamesChanged = true
}
// Check if any of the SSH principals or TLS DNS names have changed and the
// host credentials need to be regenerated.
regenerateCertificate := checkPrincipals(conn, additionalPrincipals, dnsNames)

// If the local state matches remote state and neither principals or DNS
// names changed, nothing to do. CA is in sync.
if local.Matches(remote) && !(principalsChanged || dnsNamesChanged) {
if local.Matches(remote) && !regenerateCertificate {
return &rotationStatus{}, nil
}

Expand Down Expand Up @@ -662,7 +675,7 @@ func (process *TeleportProcess) rotate(conn *Connector, localState auth.StateV2,
// that the old node came up and missed the whole rotation
// rollback cycle.
case "", services.RotationStateStandby:
if principalsChanged || dnsNamesChanged {
if regenerateCertificate {
process.Infof("Service %v has updated principals to %q, DNS Names to %q, going to request new principals and update.", id.Role, additionalPrincipals, dnsNames)
identity, err := process.reRegister(conn, additionalPrincipals, dnsNames, remote)
if err != nil {
Expand Down
63 changes: 63 additions & 0 deletions lib/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"strconv"

"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils"

Expand Down Expand Up @@ -120,6 +121,68 @@ func (s *ServiceTestSuite) TestMonitor(c *check.C) {
c.Assert(err, check.IsNil)
}

// TestCheckPrincipals checks certificates regeneration only requests
// regeneration when the principals change.
func (s *ServiceTestSuite) TestCheckPrincipals(c *check.C) {
dataDir := c.MkDir()

// Create a test auth server to extract the server identity (SSH and TLS
// certificates).
testAuthServer, err := auth.NewTestAuthServer(auth.TestAuthServerConfig{
Dir: dataDir,
})
c.Assert(err, check.IsNil)
tlsServer, err := testAuthServer.NewTestTLSServer()
c.Assert(err, check.IsNil)
defer tlsServer.Close()

testConnector := &Connector{
ServerIdentity: tlsServer.Identity,
}

var tests = []struct {
inPrincipals []string
inDNS []string
outRegenerate bool
}{
// If nothing has been updated, don't regenerate certificate.
{
inPrincipals: []string{},
inDNS: []string{},
outRegenerate: false,
},
// Don't regenerate certificate if the node does not know it's own address.
{
inPrincipals: []string{"0.0.0.0"},
inDNS: []string{},
outRegenerate: false,
},
// If a new SSH principal is found, regenerate certificate.
{
inPrincipals: []string{"1.1.1.1"},
inDNS: []string{},
outRegenerate: true,
},
// If a new TLS DNS name is found, regenerate certificate.
{
inPrincipals: []string{},
inDNS: []string{"server.example.com"},
outRegenerate: true,
},
// Don't regenerate certificate if additional principals is already on the
// certificate.
{
inPrincipals: []string{"test-tls-server"},
inDNS: []string{},
outRegenerate: false,
},
}
for _, tt := range tests {
ok := checkPrincipals(testConnector, tt.inPrincipals, tt.inDNS)
c.Assert(ok, check.Equals, tt.outRegenerate)
}
}

func waitForStatus(diagAddr string, statusCodes ...int) error {
tickCh := time.Tick(250 * time.Millisecond)
timeoutCh := time.After(10 * time.Second)
Expand Down