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

Support ACL replication #226

Merged
merged 1 commit into from
Mar 16, 2020
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
124 changes: 97 additions & 27 deletions subcommand/server-acl-init/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -40,6 +42,7 @@ type Command struct {
flagCreateSnapshotAgentToken bool
flagCreateMeshGatewayToken bool
flagCreateACLReplicationToken bool
flagACLReplicationTokenFile string
flagConsulCACert string
flagConsulTLSServerName string
flagUseHTTPS bool
Expand Down Expand Up @@ -126,6 +129,8 @@ func (c *Command) init() {
c.flags.StringVar(&c.flagInjectK8SNSMirroringPrefix, "inject-k8s-namespace-mirroring-prefix", "",
"[Enterprise Only] Prefix that will be added to all k8s namespaces mirrored into Consul by Connect inject "+
"if mirroring is enabled.")
c.flags.StringVar(&c.flagACLReplicationTokenFile, "acl-replication-token-file", "",
"Path to file containing ACL token to be used for ACL replication. If set, ACL replication is enabled.")
c.flags.StringVar(&c.flagTimeout, "timeout", "10m",
"How long we'll try to bootstrap ACLs for before timing out, e.g. 1ms, 2s, 3m")
c.flags.StringVar(&c.flagLogLevel, "log-level", "info",
Expand Down Expand Up @@ -183,6 +188,16 @@ func (c *Command) Run(args []string) int {
if c.flagReleaseName != "" {
c.flagServerLabelSelector = fmt.Sprintf("app=consul,component=server,release=%s", c.flagReleaseName)
}
var aclReplicationToken string
if c.flagACLReplicationTokenFile != "" {
// Load the ACL replication token from file.
tokenBytes, err := ioutil.ReadFile(c.flagACLReplicationTokenFile)
if err != nil {
c.UI.Error(fmt.Sprintf("Unable to read ACL replication token from file %q: %s", c.flagACLReplicationTokenFile, err))
return 1
}
aclReplicationToken = strings.TrimSpace(string(tokenBytes))
}

var cancel context.CancelFunc
c.cmdTimeout, cancel = context.WithTimeout(context.Background(), timeout)
Expand Down Expand Up @@ -234,30 +249,41 @@ func (c *Command) Run(args []string) int {
return 1
}

// Check if we've already been bootstrapped.
bootTokenSecretName := c.withPrefix("bootstrap-acl-token")
bootstrapToken, err := c.getBootstrapToken(bootTokenSecretName)
if err != nil {
c.Log.Error(fmt.Sprintf("Unexpected error looking for preexisting bootstrap Secret: %s", err))
return 1
}

var updateServerPolicy bool
if bootstrapToken != "" {
c.Log.Info(fmt.Sprintf("ACLs already bootstrapped - retrieved bootstrap token from Secret %q", bootTokenSecretName))

// Mark that we should update the server ACL policy in case
// there are namespace related config changes. Because of the
// organization of the server token creation code, the policy
// otherwise won't be updated.
updateServerPolicy = true
var bootstrapToken string

if c.flagACLReplicationTokenFile != "" {
// If ACL replication is enabled, we don't need to ACL bootstrap the servers
// since they will be performing replication.
// We can use the replication token as our bootstrap token because it
// has permissions to create policies and tokens.
c.Log.Info("ACL replication is enabled so skipping ACL bootstrapping")
bootstrapToken = aclReplicationToken
} else {
c.Log.Info("No bootstrap token from previous installation found, continuing on to bootstrapping")
bootstrapToken, err = c.bootstrapServers(bootTokenSecretName, scheme)
// Check if we've already been bootstrapped.
bootTokenSecretName := c.withPrefix("bootstrap-acl-token")
bootstrapToken, err = c.getBootstrapToken(bootTokenSecretName)
if err != nil {
c.Log.Error(err.Error())
c.Log.Error(fmt.Sprintf("Unexpected error looking for preexisting bootstrap Secret: %s", err))
return 1
}

if bootstrapToken != "" {
c.Log.Info(fmt.Sprintf("ACLs already bootstrapped - retrieved bootstrap token from Secret %q", bootTokenSecretName))

// Mark that we should update the server ACL policy in case
// there are namespace related config changes. Because of the
// organization of the server token creation code, the policy
// otherwise won't be updated.
updateServerPolicy = true
} else {
c.Log.Info("No bootstrap token from previous installation found, continuing on to bootstrapping")
bootstrapToken, err = c.bootstrapServers(bootTokenSecretName, scheme)
if err != nil {
c.Log.Error(err.Error())
return 1
}
}
}

// For all of the next operations we'll need a Consul client.
Expand All @@ -281,6 +307,13 @@ func (c *Command) Run(args []string) int {
return 1
}

consulDC, err := c.consulDatacenter(consulClient)
if err != nil {
c.Log.Error("Error getting datacenter name", "err", err)
return 1
}
c.Log.Info("Current datacenter", "datacenter", consulDC)

// With the addition of namespaces, the ACL policies associated
// with the server tokens may need to be updated if Enterprise Consul
// users upgrade to 1.7+. This updates the policy if the bootstrap
Expand Down Expand Up @@ -338,14 +371,20 @@ func (c *Command) Run(args []string) int {
return 1
}

err = c.createACL("client", agentRules, consulClient)
err = c.createLocalACL("client", agentRules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
}
}

if c.flagAllowDNS {
// The DNS policy is attached to the anonymous token.
// If performing ACL replication, we assume that the primary datacenter
// has already created the DNS policy and attached it to the anonymous
// token. We don't want to modify the DNS policy in secondary datacenters
// because it is global and we can't create separate tokens for each
// secondary datacenter because the anonymous token is global.
lkysow marked this conversation as resolved.
Show resolved Hide resolved
if c.flagAllowDNS && c.flagACLReplicationTokenFile == "" {
err := c.configureDNSPolicies(consulClient)
if err != nil {
c.Log.Error(err.Error())
Expand All @@ -360,7 +399,7 @@ func (c *Command) Run(args []string) int {
return 1
}

err = c.createACL("catalog-sync", syncRules, consulClient)
err = c.createLocalACL("catalog-sync", syncRules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
Expand All @@ -374,23 +413,23 @@ func (c *Command) Run(args []string) int {
return 1
}

err = c.createACL("connect-inject", injectRules, consulClient)
err = c.createLocalACL("connect-inject", injectRules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
}
}

if c.flagCreateEntLicenseToken {
err := c.createACL("enterprise-license", entLicenseRules, consulClient)
err := c.createLocalACL("enterprise-license", entLicenseRules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
}
}

if c.flagCreateSnapshotAgentToken {
err := c.createACL("client-snapshot-agent", snapshotAgentRules, consulClient)
err := c.createLocalACL("client-snapshot-agent", snapshotAgentRules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
Expand All @@ -404,7 +443,9 @@ func (c *Command) Run(args []string) int {
return 1
}

err = c.createACL("mesh-gateway", meshGatewayRules, consulClient)
// Mesh gateways require a global policy/token because they must
// discover services in other datacenters.
err = c.createGlobalACL("mesh-gateway", meshGatewayRules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
Expand All @@ -425,7 +466,7 @@ func (c *Command) Run(args []string) int {
c.Log.Error("Error templating acl replication token rules", "err", err)
return 1
}
err = c.createACL("acl-replication", rules, consulClient)
err = c.createGlobalACL("acl-replication", rules, consulDC, consulClient)
if err != nil {
c.Log.Error(err.Error())
return 1
Expand Down Expand Up @@ -511,3 +552,32 @@ Usage: consul-k8s server-acl-init [options]
and safe to run multiple times.

`

// consulDatacenter returns the current datacenter name using the
// /agent/self API endpoint.
func (c *Command) consulDatacenter(client *api.Client) (string, error) {
var agentCfg map[string]map[string]interface{}
err := c.untilSucceeds("calling /agent/self to get datacenter",
func() error {
var opErr error
agentCfg, opErr = client.Agent().Self()
return opErr
})
if err != nil {
return "", err
}
if _, ok := agentCfg["Config"]; !ok {
return "", fmt.Errorf("/agent/self response did not contain Config key: %s", agentCfg)
}
if _, ok := agentCfg["Config"]["Datacenter"]; !ok {
return "", fmt.Errorf("/agent/self response did not contain Config.Datacenter key: %s", agentCfg)
}
dc, ok := agentCfg["Config"]["Datacenter"].(string)
if !ok {
return "", fmt.Errorf("could not cast Config.Datacenter as string: %s", agentCfg)
}
lkysow marked this conversation as resolved.
Show resolved Hide resolved
if dc == "" {
return "", fmt.Errorf("value of Config.Datacenter was empty string: %s", agentCfg)
}
return dc, nil
}
Loading