Skip to content

Commit

Permalink
Merge pull request #36 from gruntwork-io/yori-kiam-tls-management
Browse files Browse the repository at this point in the history
TLS management command
  • Loading branch information
yorinasub17 authored Apr 2, 2019
2 parents 36fe266 + 2ba554a commit ff72eab
Show file tree
Hide file tree
Showing 24 changed files with 908 additions and 261 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ install_gruntwork_utils: &install_gruntwork_utils
--packer-version ${PACKER_VERSION} \
--use-go-dep \
--go-version ${GOLANG_VERSION} \
--go-src-path ./
--go-src-path ./
version: 2
Expand Down Expand Up @@ -146,6 +146,7 @@ jobs:
--dest-path ./bin \
--ld-flags "-X main.VERSION=$CIRCLE_TAG -extldflags '-static'"
upload-github-release-assets ./bin/*
no_output_timeout: 900s

workflows:
version: 2
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ kubergrunt tls gen \
--tls-city Phoenix \
--tls-state AZ \
--tls-country US \
--version-tag v1
--secret-annotation "gruntwork.io/version=v1"
# Generate a signed TLS key pair using the previously created CA
kubergrunt tls gen \
--namespace kube-system \
Expand All @@ -372,7 +372,7 @@ kubergrunt tls gen \
--tls-city Phoenix \
--tls-state AZ \
--tls-country US \
--version-tag v1
--secret-annotation "gruntwork.io/version=v1"
```

The first command will generate a CA key pair and store it as the Secret `ca-keypair`. The `--ca` argument signals to
Expand Down
66 changes: 66 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,74 @@
package main

import (
"fmt"
"strings"

"github.com/gruntwork-io/kubergrunt/tls"
"github.com/urfave/cli"
)

// List out common flag names

const (
KubectlContextNameFlagName = "kubectl-context-name"
KubeconfigFlagName = "kubeconfig"
)

var (
tlsCommonNameFlag = cli.StringFlag{
Name: "tls-common-name",
Usage: "(Required) The name that will go into the CN (CommonName) field of the identifier.",
}
tlsOrgFlag = cli.StringFlag{
Name: "tls-org",
Usage: "(Required) The name of the company that is generating this cert.",
}
tlsOrgUnitFlag = cli.StringFlag{
Name: "tls-org-unit",
Usage: "The name of the unit in --tls-org that is generating this cert.",
}
tlsCityFlag = cli.StringFlag{
Name: "tls-city",
Usage: "The city where --tls-org is located.",
}
tlsStateFlag = cli.StringFlag{
Name: "tls-state",
Usage: "The state where --tls-org is located.",
}
tlsCountryFlag = cli.StringFlag{
Name: "tls-country",
Usage: "The country where --tls-org is located.",
}
tlsValidityFlag = cli.IntFlag{
Name: "tls-validity",
Value: 3650,
Usage: "How long the cert will be valid for, in days.",
}
tlsAlgorithmFlag = cli.StringFlag{
Name: "tls-private-key-algorithm",
Value: tls.ECDSAAlgorithm,
Usage: fmt.Sprintf(
"The name of the algorithm to use for private keys. Must be one of: %s.",
strings.Join(tls.PrivateKeyAlgorithms, ", "),
),
}
tlsECDSACurveFlag = cli.StringFlag{
Name: "tls-private-key-ecdsa-curve",
Value: tls.P256Curve,
Usage: fmt.Sprintf(
"The name of the elliptic curve to use. Should only be used if --tls-private-key-algorithm is %s. Must be one of %s.",
tls.ECDSAAlgorithm,
strings.Join(tls.KnownCurves, ", "),
),
}
tlsRSABitsFlag = cli.IntFlag{
Name: "tls-private-key-rsa-bits",
Value: tls.MinimumRSABits,
Usage: fmt.Sprintf(
"The size of the generated RSA key in bits. Should only be used if --tls-private-key-algorithm is %s. Must be at least %d.",
tls.RSAAlgorithm,
tls.MinimumRSABits,
),
}
)
59 changes: 2 additions & 57 deletions cmd/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"crypto/x509/pkix"
"fmt"
"strings"
"time"

"github.com/gruntwork-io/gruntwork-cli/entrypoint"
Expand Down Expand Up @@ -58,62 +57,8 @@ var (
Usage: "The path to the kubectl config file to use to authenticate with Kubernetes. (default: \"~/.kube/config\")",
}

// Configurations for setting up the TLS certificates
tlsCommonNameFlag = cli.StringFlag{
Name: "tls-common-name",
Usage: "(Required) The name that will go into the CN (CommonName) field of the identifier.",
}
tlsOrgFlag = cli.StringFlag{
Name: "tls-org",
Usage: "(Required) The name of the company that is generating this cert.",
}
tlsOrgUnitFlag = cli.StringFlag{
Name: "tls-org-unit",
Usage: "The name of the unit in --tls-org that is generating this cert.",
}
tlsCityFlag = cli.StringFlag{
Name: "tls-city",
Usage: "The city where --tls-org is located.",
}
tlsStateFlag = cli.StringFlag{
Name: "tls-state",
Usage: "The state where --tls-org is located.",
}
tlsCountryFlag = cli.StringFlag{
Name: "tls-country",
Usage: "The country where --tls-org is located.",
}
tlsValidityFlag = cli.IntFlag{
Name: "tls-validity",
Value: 3650,
Usage: "How long the cert will be valid for, in days.",
}
tlsAlgorithmFlag = cli.StringFlag{
Name: "tls-private-key-algorithm",
Value: tls.ECDSAAlgorithm,
Usage: fmt.Sprintf(
"The name of the algorithm to use for private keys. Must be one of: %s.",
strings.Join(tls.PrivateKeyAlgorithms, ", "),
),
}
tlsECDSACurveFlag = cli.StringFlag{
Name: "tls-private-key-ecdsa-curve",
Value: tls.P256Curve,
Usage: fmt.Sprintf(
"The name of the elliptic curve to use. Should only be used if --tls-private-key-algorithm is %s. Must be one of %s.",
tls.ECDSAAlgorithm,
strings.Join(tls.KnownCurves, ", "),
),
}
tlsRSABitsFlag = cli.IntFlag{
Name: "tls-private-key-rsa-bits",
Value: tls.MinimumRSABits,
Usage: fmt.Sprintf(
"The size of the generated RSA key in bits. Should only be used if --tls-private-key-algorithm is %s. Must be at least %d.",
tls.RSAAlgorithm,
tls.MinimumRSABits,
),
}
// Configurations for setting up the TLS certificates.
// NOTE: the args for setting up the CA and server TLS certificates are defined in cmd/common.go
clientTLSCommonNameFlag = cli.StringFlag{
Name: "client-tls-common-name",
Usage: "(Required) The name that will go into the CN (CommonName) field of the identifier for the client.",
Expand Down
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func main() {
app.Commands = []cli.Command{
SetupEksCommand(),
SetupHelmCommand(),
SetupTLSCommand(),
}
entrypoint.RunApp(app)
}
196 changes: 196 additions & 0 deletions cmd/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package main

import (
"strings"

"github.com/gruntwork-io/gruntwork-cli/entrypoint"
"github.com/urfave/cli"

"github.com/gruntwork-io/kubergrunt/tls"
)

var (
// Required flags
tlsStoreNamespaceFlag = cli.StringFlag{
Name: "namespace",
Usage: "(Required) Kubernetes namespace that the generated certificates will reside in.",
}
tlsSecretNameFlag = cli.StringFlag{
Name: "secret-name",
Usage: "(Required) Name to use for the Kubernetes Secret resource that will store the generated certificates.",
}

// CA related flags
tlsGenCAFlag = cli.BoolFlag{
Name: "ca",
Usage: "When passed in, the generated certificates will be CA key pairs that can be used to issue new signed TLS certificates.",
}
tlsCASecretNameFlag = cli.StringFlag{
Name: "ca-secret-name",
Usage: "The name of the Kubernetes Secret resource that holds the CA key pair used to sign the newly generated TLS certificate key pairs. Required when generating signed key pairs.",
}
tlsCANamespaceFlag = cli.StringFlag{
Name: "ca-namespace",
Usage: "Kubernetes namespace where the CA key pair is stored in. Defaults to the passed in value for --namespace.",
}

// Flags to tag the Kubernetes secret resource
tlsSecretLabelsFlag = cli.StringSliceFlag{
Name: "secret-label",
Usage: "key=value pair to use to associate a Kubernetes Label with the generated Secret. Pass in multiple times for multiple labels.",
}
tlsSecretAnnotationsFlag = cli.StringSliceFlag{
Name: "secret-annotation",
Usage: "key=value pair to use to associate a Kubernetes Annotation with the generated Secret. Pass in multiple times for multiple annotations.",
}
tlsSecretFileNameBaseFlag = cli.StringFlag{
Name: "secret-filename-base",
Usage: "Basename to use for the TLS certificate key pair file names when storing in the Kubernetes Secret resource. Defaults to ca when generating CA certs, and tls otherwise.",
}

// NOTE: Configurations for setting up the TLS certificates are defined in cmd/common.go

// Configurations for how to authenticate with the Kubernetes cluster.
// NOTE: this is the same as eksKubectlContextNameFlag and eksKubeconfigFlag, except the descriptions are updated to
// fit this series of subcommands.
tlsKubectlContextNameFlag = cli.StringFlag{
Name: KubectlContextNameFlagName,
Usage: "The name to use for the config context that is set up to authenticate with the Kubernetes cluster.",
}
tlsKubeconfigFlag = cli.StringFlag{
Name: KubeconfigFlagName,
Usage: "The path to the kubectl config file to setup. Defaults to ~/.kube/config",
}
)

func SetupTLSCommand() cli.Command {
const helpText = "Helper commands to manage TLS certificate key pairs as Kubernetes Secrets."
return cli.Command{
Name: "tls",
Usage: helpText,
Description: helpText,
Subcommands: cli.Commands{
cli.Command{
Name: "gen",
Usage: "Generate new certificate key pairs.",
Description: `Generate new certificate key pairs based on the provided configuration arguments. Once the certificate is generated, it will be stored on your Kubernetes cluster as a Kuberentes Secret resource.
You can generate a CA key pair using the --ca option.
Pass in a --ca-secret-name to sign the newly generated TLS key pair using the CA key pair stored in the Secret with the name provided by --ca-secret-name.`,
Action: generateTLSCertEntrypoint,
Flags: []cli.Flag{
// Secret config flags
tlsStoreNamespaceFlag,
tlsSecretNameFlag,
tlsSecretLabelsFlag,
tlsSecretAnnotationsFlag,

// TLS config flags
tlsGenCAFlag,
tlsCASecretNameFlag,
tlsCANamespaceFlag,
tlsCommonNameFlag,
tlsOrgFlag,
tlsOrgUnitFlag,
tlsCityFlag,
tlsStateFlag,
tlsCountryFlag,
tlsValidityFlag,
tlsAlgorithmFlag,
tlsECDSACurveFlag,
tlsRSABitsFlag,

// Kubernetes auth flags
tlsKubectlContextNameFlag,
tlsKubeconfigFlag,
},
},
},
}
}

// generateTLSCertEntrypoint will parse the CLI args and then call GenerateAndStoreAsK8SSecret.
func generateTLSCertEntrypoint(cliContext *cli.Context) error {
// Extract required args
tlsSecretNamespace, err := entrypoint.StringFlagRequiredE(cliContext, tlsStoreNamespaceFlag.Name)
if err != nil {
return err
}
tlsSecretName, err := entrypoint.StringFlagRequiredE(cliContext, tlsSecretNameFlag.Name)
if err != nil {
return err
}

// Extract CA options
genCA := cliContext.Bool(tlsGenCAFlag.Name)
// caSecretName is required when genCA is false, and ignored when it is true
caSecretName := ""
if !genCA {
caSecretName, err = entrypoint.StringFlagRequiredE(cliContext, tlsCASecretNameFlag.Name)
if err != nil {
return err
}
}
// CA Secret Namespace defaults to the same as --namespace
caSecretNamespace := cliContext.String(tlsCANamespaceFlag.Name)
if caSecretNamespace == "" {
caSecretNamespace = tlsSecretNamespace
}

// Extract structs based on multiple args
kubectlOptions, err := parseKubectlOptions(cliContext)
if err != nil {
return err
}
tlsOptions, err := parseTLSArgs(cliContext, false)
if err != nil {
return err
}

// Extract optional flags
tlsSecretLabels := cliContext.StringSlice(tlsSecretLabelsFlag.Name)
tlsSecretAnnotations := cliContext.StringSlice(tlsSecretAnnotationsFlag.Name)
tlsSecretFileNameBase := cliContext.String(tlsSecretFileNameBaseFlag.Name)
if tlsSecretFileNameBase == "" && genCA {
tlsSecretFileNameBase = "ca"
} else if tlsSecretFileNameBase == "" && !genCA {
tlsSecretFileNameBase = "tls"
}

// Convert flags to structs
tlsSecretOptions := tls.KubernetesSecretOptions{
Name: tlsSecretName,
Namespace: tlsSecretNamespace,
Labels: tagArgsToMap(tlsSecretLabels),
Annotations: tagArgsToMap(tlsSecretAnnotations),
}
tlsCASecretOptions := tls.KubernetesSecretOptions{
Name: caSecretName,
Namespace: caSecretNamespace,
Labels: map[string]string{},
Annotations: map[string]string{},
}

return tls.GenerateAndStoreAsK8SSecret(
kubectlOptions,
tlsSecretOptions,
tlsCASecretOptions,
genCA,
tlsSecretFileNameBase,
tlsOptions,
)
}

// tagArgsToMap takes args used for tags (e.g --secret-label) encoded as a string slice of key=value strings and
// converts to a map.
func tagArgsToMap(tagArgs []string) map[string]string {
out := map[string]string{}
for _, tagArg := range tagArgs {
keyValues := strings.Split(tagArg, "=")
key := keyValues[0]
val := strings.Join(keyValues[1:], "=")
out[key] = val
}
return out
}
Loading

0 comments on commit ff72eab

Please sign in to comment.