mirror of https://github.com/kubernetes/kops.git
251 lines
5.7 KiB
Go
251 lines
5.7 KiB
Go
package fitasks
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"fmt"
|
|
"github.com/golang/glog"
|
|
"k8s.io/kube-deploy/upup/pkg/fi"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
var wellKnownCertificateTypes = map[string]string{
|
|
"client": "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
|
|
"server": "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
|
|
}
|
|
|
|
//go:generate fitask -type=Keypair
|
|
type Keypair struct {
|
|
Name *string
|
|
Subject string `json:"subject"`
|
|
Type string `json:"type"`
|
|
AlternateNames []string `json:"alternateNames"`
|
|
AlternateNameTasks []fi.Task `json:"alternateNameTasks"`
|
|
}
|
|
|
|
var _ fi.HasCheckExisting = &Keypair{}
|
|
|
|
// It's important always to check for the existing key, so we don't regenerate keys e.g. on terraform
|
|
func (e *Keypair) CheckExisting(c *fi.Context) bool {
|
|
return true
|
|
}
|
|
|
|
func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
|
|
castore := c.CAStore
|
|
|
|
name := fi.StringValue(e.Name)
|
|
if name == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
cert, err := castore.FindCert(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cert == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
key, err := castore.FindPrivateKey(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if key == nil {
|
|
return nil, fmt.Errorf("found cert in store, but did not find private key: %q", name)
|
|
}
|
|
|
|
var alternateNames []string
|
|
alternateNames = append(alternateNames, cert.Certificate.DNSNames...)
|
|
alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...)
|
|
for _, ip := range cert.Certificate.IPAddresses {
|
|
alternateNames = append(alternateNames, ip.String())
|
|
}
|
|
sort.Strings(alternateNames)
|
|
|
|
actual := &Keypair{
|
|
Name: &name,
|
|
Subject: pkixNameToString(&cert.Subject),
|
|
AlternateNames: alternateNames,
|
|
Type: buildTypeDescription(cert.Certificate),
|
|
}
|
|
|
|
return actual, nil
|
|
}
|
|
|
|
func (e *Keypair) Run(c *fi.Context) error {
|
|
err := e.normalize(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fi.DefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (e *Keypair) normalize(c *fi.Context) error {
|
|
var alternateNames []string
|
|
|
|
for _, s := range e.AlternateNames {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
continue
|
|
}
|
|
alternateNames = append(alternateNames, s)
|
|
}
|
|
|
|
for _, task := range e.AlternateNameTasks {
|
|
if hasAddress, ok := task.(fi.HasAddress); ok {
|
|
address, err := hasAddress.FindAddress(c)
|
|
if err != nil {
|
|
return fmt.Errorf("error finding address for %v: %v", task, err)
|
|
}
|
|
if address == nil {
|
|
glog.Warningf("Task did not have an address: %v", task)
|
|
continue
|
|
}
|
|
glog.V(8).Infof("Resolved alternateName %q for %q", *address, task)
|
|
alternateNames = append(alternateNames, *address)
|
|
} else {
|
|
return fmt.Errorf("Unsupported type for AlternateNameDependencies: %v", task)
|
|
}
|
|
}
|
|
|
|
sort.Strings(alternateNames)
|
|
e.AlternateNames = alternateNames
|
|
e.AlternateNameTasks = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Keypair) CheckChanges(a, e, changes *Keypair) error {
|
|
if a != nil {
|
|
if changes.Name != nil {
|
|
return fi.CannotChangeField("Name")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
|
|
name := fi.StringValue(e.Name)
|
|
if name == "" {
|
|
return fi.RequiredField("Name")
|
|
}
|
|
|
|
castore := c.CAStore
|
|
|
|
template, err := buildCertificateTemplate(e.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
subjectPkix, err := parsePkixName(e.Subject)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing Subject: %v", err)
|
|
}
|
|
|
|
if len(subjectPkix.ToRDNSequence()) == 0 {
|
|
return fmt.Errorf("Subject name was empty for SSL keypair %q", name)
|
|
}
|
|
|
|
template.Subject = *subjectPkix
|
|
|
|
var alternateNames []string
|
|
alternateNames = append(alternateNames, e.AlternateNames...)
|
|
|
|
for _, san := range alternateNames {
|
|
san = strings.TrimSpace(san)
|
|
if san == "" {
|
|
continue
|
|
}
|
|
if ip := net.ParseIP(san); ip != nil {
|
|
template.IPAddresses = append(template.IPAddresses, ip)
|
|
} else {
|
|
template.DNSNames = append(template.DNSNames, san)
|
|
}
|
|
}
|
|
|
|
createCertificate := false
|
|
if a == nil {
|
|
createCertificate = true
|
|
} else if changes != nil {
|
|
if changes.AlternateNames != nil {
|
|
createCertificate = true
|
|
} else {
|
|
glog.Warningf("Ignoring changes in key: %v", fi.DebugAsJsonString(changes))
|
|
}
|
|
}
|
|
|
|
if createCertificate {
|
|
glog.V(2).Infof("Creating PKI keypair %q", name)
|
|
|
|
// TODO: Reuse private key if already exists?
|
|
cert, _, err := castore.CreateKeypair(name, template)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
glog.V(8).Infof("created certificate %v", cert)
|
|
}
|
|
|
|
// TODO: Check correct subject / flags
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildCertificateTemplate(certificateType string) (*x509.Certificate, error) {
|
|
if expanded, found := wellKnownCertificateTypes[certificateType]; found {
|
|
certificateType = expanded
|
|
}
|
|
|
|
template := &x509.Certificate{
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
|
|
tokens := strings.Split(certificateType, ",")
|
|
for _, t := range tokens {
|
|
if strings.HasPrefix(t, "KeyUsage") {
|
|
ku, found := parseKeyUsage(t)
|
|
if !found {
|
|
return nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
|
}
|
|
template.KeyUsage |= ku
|
|
} else if strings.HasPrefix(t, "ExtKeyUsage") {
|
|
ku, found := parseExtKeyUsage(t)
|
|
if !found {
|
|
return nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
|
}
|
|
template.ExtKeyUsage = append(template.ExtKeyUsage, ku)
|
|
} else {
|
|
return nil, fmt.Errorf("unrecognized certificate option: %v", t)
|
|
}
|
|
}
|
|
|
|
return template, nil
|
|
}
|
|
|
|
func buildTypeDescription(cert *x509.Certificate) string {
|
|
var options []string
|
|
|
|
if cert.IsCA {
|
|
options = append(options, "CA")
|
|
}
|
|
|
|
options = append(options, keyUsageToString(cert.KeyUsage)...)
|
|
|
|
for _, extKeyUsage := range cert.ExtKeyUsage {
|
|
options = append(options, extKeyUsageToString(extKeyUsage))
|
|
}
|
|
|
|
sort.Strings(options)
|
|
s := strings.Join(options, ",")
|
|
|
|
for k, v := range wellKnownCertificateTypes {
|
|
if v == s {
|
|
s = k
|
|
}
|
|
}
|
|
|
|
return s
|
|
}
|