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

Allow "kops create keypair" to stage next CA cert #11252

Merged
merged 6 commits into from
Jun 19, 2021
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
4 changes: 2 additions & 2 deletions cmd/kops/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions cmd/kops/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ var (
createLong = templates.LongDesc(i18n.T(`
Create a resource:` + validResources +
`
Create a cluster, instancegroup or secret using command line parameters,
Create a cluster, instancegroup, keypair, or secret using command line parameters,
YAML configuration specification files, or stdin.
(Note: secrets cannot be created from YAML config files yet).
(Note: keypairs and secrets cannot be created from YAML config files yet).
`))

createExample = templates.Examples(i18n.T(`
Expand Down Expand Up @@ -107,6 +107,7 @@ func NewCmdCreate(f *util.Factory, out io.Writer) *cobra.Command {
// create subcommands
cmd.AddCommand(NewCmdCreateCluster(f, out))
cmd.AddCommand(NewCmdCreateInstanceGroup(f, out))
cmd.AddCommand(NewCmdCreateKeypair(f, out))
cmd.AddCommand(NewCmdCreateSecret(f, out))
return cmd
}
Expand Down
24 changes: 12 additions & 12 deletions cmd/kops/create_secret_keypair.go → cmd/kops/create_keypair.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -26,29 +26,29 @@ import (
)

var (
createSecretKeypairLong = templates.LongDesc(i18n.T(`
Create a secret keypair`))
createKeypairLong = templates.LongDesc(i18n.T(`
Create a keypair`))

createSecretKeypairExample = templates.Examples(i18n.T(`
Add a ca certificate and private key.
kops create secret keypair ca \
createKeypairExample = templates.Examples(i18n.T(`
Add a cluster CA certificate and private key.
kops create keypair ca \
--cert ~/ca.pem --key ~/ca-key.pem \
--name k8s-cluster.example.com --state s3://my-state-store
`))

createSecretKeypairShort = i18n.T(`Create a secret keypair.`)
createKeypairShort = i18n.T(`Create a keypair.`)
)

func NewCmdCreateKeypairSecret(f *util.Factory, out io.Writer) *cobra.Command {
func NewCmdCreateKeypair(f *util.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "keypair",
Short: createSecretKeypairShort,
Long: createSecretKeypairLong,
Example: createSecretKeypairExample,
Short: createKeypairShort,
Long: createKeypairLong,
Example: createKeypairExample,
}

// create subcommands
cmd.AddCommand(NewCmdCreateSecretCaCert(f, out))
cmd.AddCommand(NewCmdCreateKeypairCa(f, out))

return cmd
}
183 changes: 183 additions & 0 deletions cmd/kops/create_keypair_ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"context"
"crypto/x509/pkix"
"fmt"
"io"
"io/ioutil"
"os"
"time"

"github.com/spf13/cobra"
"k8s.io/klog/v2"

"k8s.io/kops/cmd/kops/util"
"k8s.io/kops/pkg/pki"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/utils"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
)

var (
createKeypairCaLong = templates.LongDesc(i18n.T(`
Add a cluster CA certificate and private key.
`))

createKeypairCaExample = templates.Examples(i18n.T(`
Add a cluster CA certificate and private key.
kops create keypair ca \
--cert ~/ca.pem --key ~/ca-key.pem \
--name k8s-cluster.example.com --state s3://my-state-store
`))

createKeypairCaShort = i18n.T(`Add a cluster CA cert and key`)
)

type CreateKeypairCaOptions struct {
ClusterName string
PrivateKeyPath string
CertPath string
Primary bool
}

// NewCmdCreateKeypairCa returns create ca certificate command
func NewCmdCreateKeypairCa(f *util.Factory, out io.Writer) *cobra.Command {
options := &CreateKeypairCaOptions{}

cmd := &cobra.Command{
Use: "ca",
Short: createKeypairCaShort,
Long: createKeypairCaLong,
Example: createKeypairCaExample,
Run: func(cmd *cobra.Command, args []string) {
ctx := context.TODO()

err := rootCommand.ProcessArgs(args)
if err != nil {
exitWithError(err)
}

options.ClusterName = rootCommand.ClusterName()

err = RunCreateKeypairCa(ctx, f, out, options)
if err != nil {
exitWithError(err)
}
},
}

cmd.Flags().StringVar(&options.CertPath, "cert", options.CertPath, "Path to CA certificate")
cmd.Flags().StringVar(&options.PrivateKeyPath, "key", options.PrivateKeyPath, "Path to CA private key")
cmd.Flags().BoolVar(&options.Primary, "primary", options.Primary, "Make the CA used to issue certificates")
Copy link
Member

Choose a reason for hiding this comment

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

I am struggling a bit with understanding this description.
Maybe it also makes sense to provide an example using --primary

Copy link
Member

Choose a reason for hiding this comment

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

Should there also be a warning around this one? If primary is set, and you create a new primary key directly, the rotation will not be graceful?

Copy link
Member Author

Choose a reason for hiding this comment

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

That will indeed be a disruptive change. It might be used in new-cluster situations, such as the integration tests.


return cmd
}

// RunCreateKeypairCa adds a custom ca certificate and private key
func RunCreateKeypairCa(ctx context.Context, f *util.Factory, out io.Writer, options *CreateKeypairCaOptions) error {
cluster, err := GetCluster(ctx, f, options.ClusterName)
if err != nil {
return fmt.Errorf("error getting cluster: %q: %v", options.ClusterName, err)
}

clientSet, err := f.Clientset()
if err != nil {
return fmt.Errorf("error getting clientset: %v", err)
}

keyStore, err := clientSet.KeyStore(cluster)
if err != nil {
return fmt.Errorf("error getting keystore: %v", err)
}

var privateKey *pki.PrivateKey
if options.PrivateKeyPath != "" {
options.PrivateKeyPath = utils.ExpandPath(options.PrivateKeyPath)
privateKeyBytes, err := ioutil.ReadFile(options.PrivateKeyPath)
if err != nil {
return fmt.Errorf("error reading user provided private key %q: %v", options.PrivateKeyPath, err)
}

privateKey, err = pki.ParsePEMPrivateKey(privateKeyBytes)
if err != nil {
return fmt.Errorf("error loading private key %q: %v", privateKeyBytes, err)
}
}

var cert *pki.Certificate
if options.CertPath == "" {
if privateKey == nil {
privateKey, err = pki.GeneratePrivateKey()
if err != nil {
return fmt.Errorf("error generating private key: %v", err)
}
}

req := pki.IssueCertRequest{
Type: "ca",
Subject: pkix.Name{CommonName: "cn=kubernetes"},
Serial: pki.BuildPKISerial(time.Now().UnixNano()),
PrivateKey: privateKey,
}
cert, _, _, err = pki.IssueCert(&req, nil)
if err != nil {
return fmt.Errorf("error issuing certificate: %v", err)
}
} else {
options.CertPath = utils.ExpandPath(options.CertPath)
certBytes, err := ioutil.ReadFile(options.CertPath)
if err != nil {
return fmt.Errorf("error reading user provided cert %q: %v", options.CertPath, err)
}

cert, err = pki.ParsePEMCertificate(certBytes)
if err != nil {
return fmt.Errorf("error loading certificate %q: %v", options.CertPath, err)
}
}

keyset, err := keyStore.FindKeyset(fi.CertificateIDCA)
if os.IsNotExist(err) || (err == nil && keyset == nil) {
keyset = &fi.Keyset{
Items: map[string]*fi.KeysetItem{},
}
} else if err != nil {
return fmt.Errorf("reading existing keyset: %v", err)
}

err = keyset.AddItem(cert, privateKey, options.Primary)
if err != nil {
return err
}

err = keyStore.StoreKeyset(fi.CertificateIDCA, keyset)
if err != nil {
return fmt.Errorf("error storing user provided keys %q %q: %v", options.CertPath, options.PrivateKeyPath, err)
}

if options.CertPath != "" {
klog.Infof("using user provided cert: %v\n", options.CertPath)
}
if options.PrivateKeyPath != "" {
klog.Infof("using user provided private key: %v\n", options.PrivateKeyPath)
}
return nil
}
1 change: 0 additions & 1 deletion cmd/kops/create_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ func NewCmdCreateSecret(f *util.Factory, out io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdCreateSecretPublicKey(f, out))
cmd.AddCommand(NewCmdCreateSecretDockerConfig(f, out))
cmd.AddCommand(NewCmdCreateSecretEncryptionConfig(f, out))
cmd.AddCommand(NewCmdCreateKeypairSecret(f, out))
cmd.AddCommand(NewCmdCreateSecretWeaveEncryptionConfig(f, out))
cmd.AddCommand(NewCmdCreateSecretCiliumEncryptionConfig(f, out))

Expand Down
Loading