Merge pull request #30922 from yifan-gu/tls_bootstrap_refactor

Automatic merge from submit-queue

Implement TLS bootstrap for kubelet using `--experimental-bootstrap-kubeconfig`  (2nd take)

Ref kubernetes/features#43 (comment)

cc @gtank @philips @mikedanese @aaronlevy @liggitt @deads2k @errordeveloper @justinsb 


Continue on the older PR https://github.com/kubernetes/kubernetes/pull/30094 as there are too many comments on that one and it's not loadable now.
This commit is contained in:
Kubernetes Submit Queue 2016-08-21 03:47:07 -07:00 committed by GitHub
commit a41e6e3817
10 changed files with 594 additions and 47 deletions

View File

@ -0,0 +1,259 @@
/*
Copyright 2016 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 app
import (
"crypto/x509/pkix"
"fmt"
"io/ioutil"
_ "net/http/pprof"
"os"
"path/filepath"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/certificates"
unversionedcertificates "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/fields"
utilcertificates "k8s.io/kubernetes/pkg/util/certificates"
"k8s.io/kubernetes/pkg/util/crypto"
"k8s.io/kubernetes/pkg/watch"
)
const (
defaultKubeletClientCertificateFile = "kubelet-client.crt"
defaultKubeletClientKeyFile = "kubelet-client.key"
)
// bootstrapClientCert requests a client cert for kubelet if the kubeconfigPath file does not exist.
// The kubeconfig at bootstrapPath is used to request a client certificate from the API server.
// On success, a kubeconfig file referencing the generated key and obtained certificate is written to kubeconfigPath.
// The certificate and key file are stored in certDir.
func bootstrapClientCert(kubeconfigPath string, bootstrapPath string, certDir string, nodeName string) error {
// Short-circuit if the kubeconfig file already exists.
// TODO: inspect the kubeconfig, ensure a rest client can be built from it, verify client cert expiration, etc.
_, err := os.Stat(kubeconfigPath)
if err == nil {
glog.V(2).Infof("Kubeconfig %s exists, skipping bootstrap", kubeconfigPath)
return nil
}
if !os.IsNotExist(err) {
glog.Errorf("Error reading kubeconfig %s, skipping bootstrap: %v", kubeconfigPath, err)
return err
}
glog.V(2).Info("Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file")
bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
if err != nil {
return fmt.Errorf("unable to load bootstrap kubeconfig: %v", err)
}
bootstrapClient, err := unversionedcertificates.NewForConfig(bootstrapClientConfig)
if err != nil {
return fmt.Errorf("unable to create certificates signing request client: %v", err)
}
success := false
// Get the private key.
keyPath, err := filepath.Abs(filepath.Join(certDir, defaultKubeletClientKeyFile))
if err != nil {
return fmt.Errorf("unable to build bootstrap key path: %v", err)
}
keyData, generatedKeyFile, err := loadOrGenerateKeyFile(keyPath)
if err != nil {
return err
}
if generatedKeyFile {
defer func() {
if !success {
if err := os.Remove(keyPath); err != nil {
glog.Warningf("Cannot clean up the key file %q: %v", keyPath, err)
}
}
}()
}
// Get the cert.
certPath, err := filepath.Abs(filepath.Join(certDir, defaultKubeletClientCertificateFile))
if err != nil {
return fmt.Errorf("unable to build bootstrap client cert path: %v", err)
}
certData, err := RequestClientCertificate(bootstrapClient.CertificateSigningRequests(), keyData, nodeName)
if err != nil {
return err
}
if err := crypto.WriteCertToPath(certPath, certData); err != nil {
return err
}
defer func() {
if !success {
if err := os.Remove(certPath); err != nil {
glog.Warningf("Cannot clean up the cert file %q: %v", certPath, err)
}
}
}()
// Get the CA data from the bootstrap client config.
caFile, caData := bootstrapClientConfig.CAFile, []byte{}
if len(caFile) == 0 {
caData = bootstrapClientConfig.CAData
}
// Build resulting kubeconfig.
kubeconfigData := clientcmdapi.Config{
// Define a cluster stanza based on the bootstrap kubeconfig.
Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": {
Server: bootstrapClientConfig.Host,
InsecureSkipTLSVerify: bootstrapClientConfig.Insecure,
CertificateAuthority: caFile,
CertificateAuthorityData: caData,
}},
// Define auth based on the obtained client cert.
AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": {
ClientCertificate: certPath,
ClientKey: keyPath,
}},
// Define a context that connects the auth info and cluster, and set it as the default
Contexts: map[string]*clientcmdapi.Context{"default-context": {
Cluster: "default-cluster",
AuthInfo: "default-auth",
Namespace: "default",
}},
CurrentContext: "default-context",
}
// Marshal to disk
if err := clientcmd.WriteToFile(kubeconfigData, kubeconfigPath); err != nil {
return err
}
success = true
return nil
}
func loadRESTClientConfig(kubeconfig string) (*restclient.Config, error) {
// Load structured kubeconfig data from the given path.
loader := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}
loadedConfig, err := loader.Load()
if err != nil {
return nil, err
}
// Flatten the loaded data to a particular restclient.Config based on the current context.
return clientcmd.NewNonInteractiveClientConfig(
*loadedConfig,
loadedConfig.CurrentContext,
&clientcmd.ConfigOverrides{},
loader,
).ClientConfig()
}
func loadOrGenerateKeyFile(keyPath string) (data []byte, wasGenerated bool, err error) {
loadedData, err := ioutil.ReadFile(keyPath)
if err == nil {
return loadedData, false, err
}
if !os.IsNotExist(err) {
return nil, false, fmt.Errorf("error loading key from %s: %v", keyPath, err)
}
generatedData, err := utilcertificates.GeneratePrivateKey()
if err != nil {
return nil, false, fmt.Errorf("error generating key: %v", err)
}
if err := crypto.WriteKeyToPath(keyPath, generatedData); err != nil {
return nil, false, fmt.Errorf("error writing key to %s: %v", keyPath, err)
}
return generatedData, true, nil
}
// RequestClientCertificate will create a certificate signing request and send it to API server,
// then it will watch the object's status, once approved by API server, it will return the API
// server's issued certificate (pem-encoded). If there is any errors, or the watch timeouts,
// it will return an error.
func RequestClientCertificate(client unversionedcertificates.CertificateSigningRequestInterface, privateKeyData []byte, nodeName string) (certData []byte, err error) {
subject := &pkix.Name{
Organization: []string{"system:nodes"},
CommonName: fmt.Sprintf("system:node:%s", nodeName),
}
privateKey, err := utilcertificates.ParsePrivateKey(privateKeyData)
if err != nil {
return nil, fmt.Errorf("invalid private key for certificate request: %v", err)
}
csr, err := utilcertificates.NewCertificateRequest(privateKey, subject, nil, nil)
if err != nil {
return nil, fmt.Errorf("unable to generate certificate request: %v", err)
}
req, err := client.Create(&certificates.CertificateSigningRequest{
// Username, UID, Groups will be injected by API server.
TypeMeta: unversioned.TypeMeta{Kind: "CertificateSigningRequest"},
ObjectMeta: api.ObjectMeta{GenerateName: "csr-"},
// TODO: For now, this is a request for a certificate with allowed usage of "TLS Web Client Authentication".
// Need to figure out whether/how to surface the allowed usage in the spec.
Spec: certificates.CertificateSigningRequestSpec{Request: csr},
})
if err != nil {
return nil, fmt.Errorf("cannot create certificate signing request: %v", err)
}
// Make a default timeout = 3600s.
var defaultTimeoutSeconds int64 = 3600
resultCh, err := client.Watch(api.ListOptions{
Watch: true,
TimeoutSeconds: &defaultTimeoutSeconds,
FieldSelector: fields.OneTermEqualSelector("metadata.name", req.Name),
})
if err != nil {
return nil, fmt.Errorf("cannot watch on the certificate signing request: %v", err)
}
var status certificates.CertificateSigningRequestStatus
ch := resultCh.ResultChan()
for {
event, ok := <-ch
if !ok {
break
}
if event.Type == watch.Modified || event.Type == watch.Added {
if event.Object.(*certificates.CertificateSigningRequest).UID != req.UID {
continue
}
status = event.Object.(*certificates.CertificateSigningRequest).Status
for _, c := range status.Conditions {
if c.Type == certificates.CertificateDenied {
return nil, fmt.Errorf("certificate signing request is not approved, reason: %v, message: %v", c.Reason, c.Message)
}
if c.Type == certificates.CertificateApproved && status.Certificate != nil {
return status.Certificate, nil
}
}
}
}
return nil, fmt.Errorf("watch channel closed")
}

View File

@ -0,0 +1,85 @@
/*
Copyright 2016 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 app
import (
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/util/diff"
)
func TestLoadRESTClientConfig(t *testing.T) {
testData := []byte(`
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: ca-a.crt
server: https://cluster-a.com
name: cluster-a
- cluster:
certificate-authority-data: VGVzdA==
server: https://cluster-b.com
name: cluster-b
contexts:
- context:
cluster: cluster-a
namespace: ns-a
user: user-a
name: context-a
- context:
cluster: cluster-b
namespace: ns-b
user: user-b
name: context-b
current-context: context-b
users:
- name: user-a
user:
token: mytoken-a
- name: user-b
user:
token: mytoken-b
`)
f, err := ioutil.TempFile("", "kubeconfig")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
ioutil.WriteFile(f.Name(), testData, os.FileMode(0755))
config, err := loadRESTClientConfig(f.Name())
if err != nil {
t.Fatal(err)
}
expectedConfig := &restclient.Config{
Host: "https://cluster-b.com",
TLSClientConfig: restclient.TLSClientConfig{
CAData: []byte(`Test`),
},
BearerToken: "mytoken-b",
}
if !reflect.DeepEqual(config, expectedConfig) {
t.Errorf("Unexpected config: %s", diff.ObjectDiff(config, expectedConfig))
}
}

View File

@ -41,7 +41,9 @@ const (
type KubeletServer struct {
componentconfig.KubeletConfiguration
KubeConfig util.StringFlag
KubeConfig util.StringFlag
BootstrapKubeconfig string
// If true, an invalid KubeConfig will result in the Kubelet exiting with an error.
RequireKubeConfig bool
AuthPath util.StringFlag // Deprecated -- use KubeConfig instead
@ -97,6 +99,10 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.TLSPrivateKeyFile, "tls-private-key-file", s.TLSPrivateKeyFile, "File containing x509 private key matching --tls-cert-file.")
fs.StringVar(&s.CertDirectory, "cert-dir", s.CertDirectory, "The directory where the TLS certs are located (by default /var/run/kubernetes). "+
"If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.")
fs.StringVar(&s.BootstrapKubeconfig, "experimental-bootstrap-kubeconfig", s.BootstrapKubeconfig, "<Warning: Experimental feature> Path to a kubeconfig file that will be used to get client certificate for kubelet. "+
"If the file specified by --kubeconfig does not exist, the bootstrap kubeconfig is used to request a client certificate from the API server. "+
"On success, a kubeconfig file referencing the generated key and obtained certificate is written to the path specified by --kubeconfig. "+
"The certificate and key file will be stored in the directory pointed by --cert-dir.")
fs.StringVar(&s.HostnameOverride, "hostname-override", s.HostnameOverride, "If non-empty, will use this string as identification instead of the actual hostname.")
fs.StringVar(&s.PodInfraContainerImage, "pod-infra-container-image", s.PodInfraContainerImage, "The image whose network/ipc namespaces containers in each pod will use.")
fs.StringVar(&s.DockerEndpoint, "docker-endpoint", s.DockerEndpoint, "Use this for the docker endpoint to communicate with")

View File

@ -350,6 +350,33 @@ func run(s *options.KubeletServer, kcfg *KubeletConfig) (err error) {
if kcfg == nil {
var kubeClient, eventClient *clientset.Clientset
var autoDetectCloudProvider bool
var cloud cloudprovider.Interface
if s.CloudProvider == kubeExternal.AutoDetectCloudProvider {
autoDetectCloudProvider = true
} else {
cloud, err = cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
if err != nil {
return err
}
if cloud == nil {
glog.V(2).Infof("No cloud provider specified: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
} else {
glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
}
}
if s.BootstrapKubeconfig != "" {
nodeName, err := getNodeName(cloud, nodeutil.GetHostname(s.HostnameOverride))
if err != nil {
return err
}
if err := bootstrapClientCert(s.KubeConfig.Value(), s.BootstrapKubeconfig, s.CertDirectory, nodeName); err != nil {
return err
}
}
clientConfig, err := CreateAPIServerClientConfig(s)
if err == nil {
kubeClient, err = clientset.NewForConfig(clientConfig)
@ -374,24 +401,12 @@ func run(s *options.KubeletServer, kcfg *KubeletConfig) (err error) {
if err != nil {
return err
}
kcfg = cfg
kcfg.AutoDetectCloudProvider = autoDetectCloudProvider
kcfg.Cloud = cloud
kcfg.KubeClient = kubeClient
kcfg.EventClient = eventClient
if s.CloudProvider == kubeExternal.AutoDetectCloudProvider {
kcfg.AutoDetectCloudProvider = true
} else {
cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
if err != nil {
return err
}
if cloud == nil {
glog.V(2).Infof("No cloud provider specified: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
} else {
glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
kcfg.Cloud = cloud
}
}
}
if kcfg.CAdvisorInterface == nil {
@ -454,13 +469,35 @@ func run(s *options.KubeletServer, kcfg *KubeletConfig) (err error) {
return nil
}
// getNodeName returns the node name according to the cloud provider
// if cloud provider is specified. Otherwise, returns the hostname of the node.
func getNodeName(cloud cloudprovider.Interface, hostname string) (string, error) {
if cloud == nil {
return hostname, nil
}
instances, ok := cloud.Instances()
if !ok {
return "", fmt.Errorf("failed to get instances from cloud provider")
}
nodeName, err := instances.CurrentNodeName(hostname)
if err != nil {
return "", fmt.Errorf("error fetching current instance name from cloud provider: %v", err)
}
glog.V(2).Infof("cloud provider determined current node name to be %s", nodeName)
return nodeName, nil
}
// InitializeTLS checks for a configured TLSCertFile and TLSPrivateKeyFile: if unspecified a new self-signed
// certificate and key file are generated. Returns a configured server.TLSOptions object.
func InitializeTLS(s *options.KubeletServer) (*server.TLSOptions, error) {
if s.TLSCertFile == "" && s.TLSPrivateKeyFile == "" {
s.TLSCertFile = path.Join(s.CertDirectory, "kubelet.crt")
s.TLSPrivateKeyFile = path.Join(s.CertDirectory, "kubelet.key")
if crypto.ShouldGenSelfSignedCerts(s.TLSCertFile, s.TLSPrivateKeyFile) {
if !crypto.FoundCertOrKey(s.TLSCertFile, s.TLSPrivateKeyFile) {
if err := crypto.GenerateSelfSignedCert(nodeutil.GetHostname(s.HostnameOverride), s.TLSCertFile, s.TLSPrivateKeyFile, nil, nil); err != nil {
return nil, fmt.Errorf("unable to generate self signed cert: %v", err)
}
@ -693,23 +730,10 @@ func RunKubelet(kcfg *KubeletConfig) error {
kcfg.Hostname = nodeutil.GetHostname(kcfg.HostnameOverride)
if len(kcfg.NodeName) == 0 {
// Query the cloud provider for our node name, default to Hostname
nodeName := kcfg.Hostname
if kcfg.Cloud != nil {
var err error
instances, ok := kcfg.Cloud.Instances()
if !ok {
return fmt.Errorf("failed to get instances from cloud provider")
}
nodeName, err = instances.CurrentNodeName(kcfg.Hostname)
if err != nil {
return fmt.Errorf("error fetching current instance name from cloud provider: %v", err)
}
glog.V(2).Infof("cloud provider determined current node name to be %s", nodeName)
nodeName, err := getNodeName(kcfg.Cloud, kcfg.Hostname)
if err != nil {
return err
}
kcfg.NodeName = nodeName
}

View File

@ -163,6 +163,7 @@ executor-logv
executor-path
executor-suicide-timeout
exit-on-lock-contention
experimental-bootstrap-kubeconfig
experimental-flannel-overlay
experimental-keystone-url
experimental-nvidia-gpus

View File

@ -723,7 +723,7 @@ func (s *GenericAPIServer) Run(options *options.ServerRunOptions) {
alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes"}
// It would be nice to set a fqdn subject alt name, but only the kubelets know, the apiserver is clueless
// alternateDNS = append(alternateDNS, "kubernetes.default.svc.CLUSTER.DNS.NAME")
if crypto.ShouldGenSelfSignedCerts(options.TLSCertFile, options.TLSPrivateKeyFile) {
if !crypto.FoundCertOrKey(options.TLSCertFile, options.TLSPrivateKeyFile) {
if err := crypto.GenerateSelfSignedCert(s.ClusterIP.String(), options.TLSCertFile, options.TLSPrivateKeyFile, alternateIPs, alternateDNS); err != nil {
glog.Errorf("Unable to generate self signed cert: %v", err)
} else {

View File

@ -17,9 +17,16 @@ limitations under the License.
package certificates
import (
"crypto/ecdsa"
"crypto/elliptic"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"net"
"k8s.io/kubernetes/pkg/apis/certificates"
)
@ -38,3 +45,94 @@ func ParseCertificateRequestObject(obj *certificates.CertificateSigningRequest)
}
return csr, nil
}
// GeneratePrivateKey returns PEM data containing a generated ECDSA private key
func GeneratePrivateKey() ([]byte, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
if err != nil {
return nil, err
}
derBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return nil, err
}
privateKeyPemBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: derBytes,
}
return pem.EncodeToMemory(privateKeyPemBlock), nil
}
// ParsePrivateKey returns a private key parsed from a PEM block in the supplied data.
// Recognizes PEM blocks for "EC PRIVATE KEY" and "RSA PRIVATE KEY"
func ParsePrivateKey(keyData []byte) (interface{}, error) {
for {
var privateKeyPemBlock *pem.Block
privateKeyPemBlock, keyData = pem.Decode(keyData)
if privateKeyPemBlock == nil {
// we read all the PEM blocks and didn't recognize one
return nil, fmt.Errorf("no private key PEM block found")
}
switch privateKeyPemBlock.Type {
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes)
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes)
}
}
}
// NewCertificateRequest generates a PEM-encoded CSR using the supplied private key, subject, and SANs.
// privateKey must be a *ecdsa.PrivateKey or *rsa.PrivateKey.
func NewCertificateRequest(privateKey interface{}, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, err error) {
var sigType x509.SignatureAlgorithm
switch privateKey := privateKey.(type) {
case *ecdsa.PrivateKey:
switch privateKey.Curve {
case elliptic.P224(), elliptic.P256():
sigType = x509.ECDSAWithSHA256
case elliptic.P384():
sigType = x509.ECDSAWithSHA384
case elliptic.P521():
sigType = x509.ECDSAWithSHA512
default:
return nil, fmt.Errorf("unknown elliptic curve: %v", privateKey.Curve)
}
case *rsa.PrivateKey:
keySize := privateKey.N.BitLen()
switch {
case keySize >= 4096:
sigType = x509.SHA512WithRSA
case keySize >= 3072:
sigType = x509.SHA384WithRSA
default:
sigType = x509.SHA256WithRSA
}
default:
return nil, fmt.Errorf("unsupported key type: %T", privateKey)
}
template := &x509.CertificateRequest{
Subject: *subject,
SignatureAlgorithm: sigType,
DNSNames: dnsSANs,
IPAddresses: ipSANs,
}
csr, err = x509.CreateCertificateRequest(cryptorand.Reader, template, privateKey)
if err != nil {
return nil, err
}
csrPemBlock := &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr,
}
return pem.EncodeToMemory(csrPemBlock), nil
}

View File

@ -0,0 +1,46 @@
/*
Copyright 2016 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 certificates
import (
"crypto/x509/pkix"
"io/ioutil"
"net"
"testing"
)
func TestNewCertificateRequest(t *testing.T) {
keyFile := "testdata/dontUseThisKey.pem"
subject := &pkix.Name{
CommonName: "kube-worker",
}
dnsSANs := []string{"localhost"}
ipSANs := []net.IP{net.ParseIP("127.0.0.1")}
keyData, err := ioutil.ReadFile(keyFile)
if err != nil {
t.Fatal(err)
}
key, err := ParsePrivateKey(keyData)
if err != nil {
t.Fatal(err)
}
_, err = NewCertificateRequest(key, subject, dnsSANs, ipSANs)
if err != nil {
t.Error(err)
}
}

View File

@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDAPEbSXwyDfWf0+61Oofd7aHkmdX69mrzD2Xb1CHF5syfsoRIhnG0dJ
ozBulPZCDDWgBwYFK4EEACKhZANiAATjlMJAtKhEPqU/i7MsrgKcK/RmXHC6He7W
0p69+9qFXg2raJ9zvvbKxkiu2ELOYRDAz0utcFTBOIgoUJEzBVmsjZQ7dvFa1BKP
Ym7MFAKG3O2espBqXn+audgdHGh5B0I=
-----END EC PRIVATE KEY-----

View File

@ -33,14 +33,14 @@ import (
"time"
)
// ShouldGenSelfSignedCerts returns false if the certificate or key files already exists,
// otherwise returns true.
func ShouldGenSelfSignedCerts(certPath, keyPath string) bool {
// FoundCertOrKey returns true if the certificate or key files already exists,
// otherwise returns false.
func FoundCertOrKey(certPath, keyPath string) bool {
if canReadFile(certPath) || canReadFile(keyPath) {
return false
return true
}
return true
return false
}
// If the file represented by path exists and
@ -109,24 +109,46 @@ func GenerateSelfSignedCert(host, certPath, keyPath string, alternateIPs []net.I
}
// Write cert
if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil {
return err
}
if err := ioutil.WriteFile(certPath, certBuffer.Bytes(), os.FileMode(0644)); err != nil {
if err := WriteCertToPath(certPath, certBuffer.Bytes()); err != nil {
return err
}
// Write key
if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil {
return err
}
if err := ioutil.WriteFile(keyPath, keyBuffer.Bytes(), os.FileMode(0600)); err != nil {
if err := WriteKeyToPath(keyPath, keyBuffer.Bytes()); err != nil {
return err
}
return nil
}
// WriteCertToPath writes the pem-encoded certificate data to certPath.
// The certificate file will be created with file mode 0644.
// If the certificate file already exists, it will be overwritten.
// The parent directory of the certPath will be created as needed with file mode 0755.
func WriteCertToPath(certPath string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil {
return err
}
if err := ioutil.WriteFile(certPath, data, os.FileMode(0644)); err != nil {
return err
}
return nil
}
// WriteKeyToPath writes the pem-encoded key data to keyPath.
// The key file will be created with file mode 0600.
// If the key file already exists, it will be overwritten.
// The parent directory of the keyPath will be created as needed with file mode 0755.
func WriteKeyToPath(keyPath string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil {
return err
}
if err := ioutil.WriteFile(keyPath, data, os.FileMode(0600)); err != nil {
return err
}
return nil
}
// CertPoolFromFile returns an x509.CertPool containing the certificates in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates
func CertPoolFromFile(filename string) (*x509.CertPool, error) {