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

Various TLS & Sync related fixes #186

Merged
merged 6 commits into from
Jun 19, 2018
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ metadata:
name: "replication-from-a-to-b"
spec:
source:
endpoint: ["https://163.172.149.229:31888", "https://51.15.225.110:31888", "https://51.15.229.133:31888"]
masterEndpoint: ["https://163.172.149.229:31888", "https://51.15.225.110:31888", "https://51.15.229.133:31888"]
auth:
keyfileSecretName: cluster-a-sync-auth
tls:
Expand Down Expand Up @@ -67,7 +67,7 @@ with sync enabled.

This cluster configured as the replication source.

### `spec.source.endpoint: []string`
### `spec.source.masterEndpoint: []string`

This setting specifies zero or more master endpoint URL's of the source cluster.

Expand Down Expand Up @@ -108,7 +108,7 @@ with sync enabled.

This cluster configured as the replication destination.

### `spec.destination.endpoint: []string`
### `spec.destination.masterEndpoint: []string`

This setting specifies zero or more master endpoint URL's of the destination cluster.

Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/deployment/v1alpha/sync_external_access_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
type SyncExternalAccessSpec struct {
ExternalAccessSpec
MasterEndpoint []string `json:"masterEndpoint,omitempty"`
AccessPackageSecretNames []string `json:accessPackageSecretNames,omitempty"`
AccessPackageSecretNames []string `json:"accessPackageSecretNames,omitempty"`
}

// GetMasterEndpoint returns the value of masterEndpoint.
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/replication/v1alpha/endpoint_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ type EndpointSpec struct {
// DeploymentName holds the name of an ArangoDeployment resource.
// If set this provides default values for masterEndpoint, auth & tls.
DeploymentName *string `json:"deploymentName,omitempty"`
// Endpoint holds a list of URLs used to reach the syncmaster(s).
Endpoint []string `json:"endpoint,omitempty"`
// MasterEndpoint holds a list of URLs used to reach the syncmaster(s).
MasterEndpoint []string `json:"masterEndpoint,omitempty"`
// Authentication holds settings needed to authentication at the syncmaster.
Authentication EndpointAuthenticationSpec `json:"auth"`
// TLS holds settings needed to verify the TLS connection to the syncmaster.
Expand All @@ -60,13 +60,13 @@ func (s EndpointSpec) Validate(isSourceEndpoint bool) error {
if err := k8sutil.ValidateOptionalResourceName(s.GetDeploymentName()); err != nil {
return maskAny(err)
}
for _, ep := range s.Endpoint {
for _, ep := range s.MasterEndpoint {
if _, err := url.Parse(ep); err != nil {
return maskAny(errors.Wrapf(ValidationError, "Invalid master endpoint '%s': %s", ep, err))
}
}
hasDeploymentName := s.HasDeploymentName()
if !hasDeploymentName && len(s.Endpoint) == 0 {
if !hasDeploymentName && len(s.MasterEndpoint) == 0 {
return maskAny(errors.Wrapf(ValidationError, "Provide a deploy name or at least one master endpoint"))
}
if err := s.Authentication.Validate(isSourceEndpoint || !hasDeploymentName); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/replication/v1alpha/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ func (in *EndpointSpec) DeepCopyInto(out *EndpointSpec) {
**out = **in
}
}
if in.Endpoint != nil {
in, out := &in.Endpoint, &out.Endpoint
if in.MasterEndpoint != nil {
in, out := &in.MasterEndpoint, &out.MasterEndpoint
*out = make([]string, len(*in))
copy(*out, *in)
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/deployment/context_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ package deployment
import (
"context"
"fmt"
"net"
"strconv"

"github.com/arangodb/arangosync/client"
"github.com/arangodb/arangosync/tasks"
Expand Down Expand Up @@ -175,7 +177,11 @@ func (d *Deployment) GetSyncServerClient(ctx context.Context, group api.ServerGr
dnsName := k8sutil.CreatePodDNSName(d.apiObject, group.AsRole(), id)

// Build client
source := client.Endpoint{dnsName}
port := k8sutil.ArangoSyncMasterPort
if group == api.ServerGroupSyncWorkers {
port = k8sutil.ArangoSyncWorkerPort
}
source := client.Endpoint{"https://" + net.JoinHostPort(dnsName, strconv.Itoa(port))}
tlsAuth := tasks.TLSAuthentication{
TLSClientAuthentication: tasks.TLSClientAuthentication{
ClientToken: monitoringToken,
Expand Down
2 changes: 1 addition & 1 deletion pkg/deployment/reconcile/action_wait_for_member_up.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (a *actionWaitForMemberUp) checkProgressCluster(ctx context.Context) (bool,
// of a sync master / worker.
func (a *actionWaitForMemberUp) checkProgressArangoSync(ctx context.Context) (bool, bool, error) {
log := a.log
c, err := a.actionCtx.GetSyncServerClient(ctx, a.action.Group, a.action.ID)
c, err := a.actionCtx.GetSyncServerClient(ctx, a.action.Group, a.action.MemberID)
if err != nil {
log.Debug().Err(err).Msg("Failed to create arangosync client")
return false, false, maskAny(err)
Expand Down
90 changes: 47 additions & 43 deletions pkg/deployment/reconcile/plan_builder_tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ func createRotateTLSServerCertificatePlan(log zerolog.Logger, spec api.Deploymen
Msg("Failed to get TLS secret")
continue
}
renewalNeeded := tlsKeyfileNeedsRenewal(log, keyfile)
tlsSpec := spec.TLS
if group.IsArangosync() {
tlsSpec = spec.Sync.TLS
}
renewalNeeded, reason := tlsKeyfileNeedsRenewal(log, keyfile, tlsSpec)
if renewalNeeded {
plan = append(append(plan,
api.NewAction(api.ActionTypeRenewTLSCertificate, group, m.ID)),
api.NewAction(api.ActionTypeRenewTLSCertificate, group, m.ID, reason)),
createRotateMemberPlan(log, m, group, "TLS certificate renewal")...,
)
}
Expand Down Expand Up @@ -124,8 +128,32 @@ func createRotateTLSCAPlan(log zerolog.Logger, spec api.DeploymentSpec, status a

// tlsKeyfileNeedsRenewal decides if the certificate in the given keyfile
// should be renewed.
func tlsKeyfileNeedsRenewal(log zerolog.Logger, keyfile string) bool {
func tlsKeyfileNeedsRenewal(log zerolog.Logger, keyfile string, spec api.TLSSpec) (bool, string) {
raw := []byte(keyfile)
// containsAll returns true when all elements in the expected list
// are in the actual list.
containsAll := func(actual []string, expected []string) bool {
for _, x := range expected {
found := false
for _, y := range actual {
if x == y {
found = true
break
}
}
if !found {
return false
}
}
return true
}
ipsToStringSlice := func(list []net.IP) []string {
result := make([]string, len(list))
for i, x := range list {
result[i] = x.String()
}
return result
}
for {
var derBlock *pem.Block
derBlock, raw = pem.Decode(raw)
Expand All @@ -137,7 +165,7 @@ func tlsKeyfileNeedsRenewal(log zerolog.Logger, keyfile string) bool {
if err != nil {
// We do not understand the certificate, let's renew it
log.Warn().Err(err).Msg("Failed to parse x509 certificate. Renewing it")
return true
return true, "Cannot parse x509 certificate: " + err.Error()
}
if cert.IsCA {
// Only look at the server certificate, not CA or intermediate
Expand All @@ -153,42 +181,31 @@ func tlsKeyfileNeedsRenewal(log zerolog.Logger, keyfile string) bool {
Str("not-after", cert.NotAfter.String()).
Str("expiration-date", expirationDate.String()).
Msg("TLS certificate renewal needed")
return true
return true, "Server certificate about to expire"
}
// Check alternate names against spec
dnsNames, ipAddresses, emailAddress, err := spec.GetParsedAltNames()
if err == nil {
if !containsAll(cert.DNSNames, dnsNames) {
return true, "Some alternate DNS names are missing"
}
if !containsAll(ipsToStringSlice(cert.IPAddresses), ipAddresses) {
return true, "Some alternate IP addresses are missing"
}
if !containsAll(cert.EmailAddresses, emailAddress) {
return true, "Some alternate email addresses are missing"
}
}
}
}
return false
return false, ""
}

// tlsCANeedsRenewal decides if the given CA certificate
// should be renewed.
// Returns: shouldRenew, reason
func tlsCANeedsRenewal(log zerolog.Logger, cert string, spec api.TLSSpec) (bool, string) {
raw := []byte(cert)
// containsAll returns true when all elements in the expected list
// are in the actual list.
containsAll := func(actual []string, expected []string) bool {
for _, x := range expected {
found := false
for _, y := range actual {
if x == y {
found = true
break
}
}
if !found {
return false
}
}
return true
}
ipsToStringSlice := func(list []net.IP) []string {
result := make([]string, len(list))
for i, x := range list {
result[i] = x.String()
}
return result
}
for {
var derBlock *pem.Block
derBlock, raw = pem.Decode(raw)
Expand Down Expand Up @@ -218,19 +235,6 @@ func tlsCANeedsRenewal(log zerolog.Logger, cert string, spec api.TLSSpec) (bool,
Msg("TLS CA certificate renewal needed")
return true, "CA Certificate about to expire"
}
// Check alternate names against spec
dnsNames, ipAddresses, emailAddress, err := spec.GetParsedAltNames()
if err == nil {
if !containsAll(cert.DNSNames, dnsNames) {
return true, "Some alternate DNS names are missing"
}
if !containsAll(ipsToStringSlice(cert.IPAddresses), ipAddresses) {
return true, "Some alternate IP addresses are missing"
}
if !containsAll(cert.EmailAddresses, emailAddress) {
return true, "Some alternate email addresses are missing"
}
}
}
}
return false, ""
Expand Down
17 changes: 5 additions & 12 deletions pkg/deployment/resources/certificates_tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,13 @@ const (
// specified in the given spec.
func createTLSCACertificate(log zerolog.Logger, cli v1.CoreV1Interface, spec api.TLSSpec, deploymentName, namespace string, ownerRef *metav1.OwnerReference) error {
log = log.With().Str("secret", spec.GetCASecretName()).Logger()
dnsNames, ipAddresses, emailAddress, err := spec.GetParsedAltNames()
if err != nil {
log.Debug().Err(err).Msg("Failed to get alternate names")
return maskAny(err)
}

options := certificates.CreateCertificateOptions{
CommonName: fmt.Sprintf("%s Root Certificate", deploymentName),
Hosts: append(dnsNames, ipAddresses...),
EmailAddresses: emailAddress,
ValidFrom: time.Now(),
ValidFor: caTTL,
IsCA: true,
ECDSACurve: tlsECDSACurve,
CommonName: fmt.Sprintf("%s Root Certificate", deploymentName),
ValidFrom: time.Now(),
ValidFor: caTTL,
IsCA: true,
ECDSACurve: tlsECDSACurve,
}
cert, priv, err := certificates.CreateCertificate(options, nil)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/replication/sync_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (dr *DeploymentReplication) createArangoSyncEndpoint(epSpec api.EndpointSpe
dnsName := k8sutil.CreateSyncMasterClientServiceDNSName(depl)
return client.Endpoint{"https://" + net.JoinHostPort(dnsName, strconv.Itoa(k8sutil.ArangoSyncMasterPort))}, nil
}
return client.Endpoint(epSpec.Endpoint), nil
return client.Endpoint(epSpec.MasterEndpoint), nil
}

// createArangoSyncTLSAuthentication creates the authentication needed to authenticate
Expand Down