| /* |
| 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 options |
| |
| import ( |
| "crypto/tls" |
| "fmt" |
| "net" |
| "path" |
| "strconv" |
| "strings" |
| |
| "github.com/golang/glog" |
| "github.com/spf13/pflag" |
| |
| utilnet "k8s.io/apimachinery/pkg/util/net" |
| "k8s.io/apiserver/pkg/server" |
| utilflag "k8s.io/apiserver/pkg/util/flag" |
| certutil "k8s.io/client-go/util/cert" |
| ) |
| |
| type SecureServingOptions struct { |
| BindAddress net.IP |
| // BindPort is ignored when Listener is set, will serve https even with 0. |
| BindPort int |
| // BindNetwork is the type of network to bind to - defaults to "tcp", accepts "tcp", |
| // "tcp4", and "tcp6". |
| BindNetwork string |
| |
| // Listener is the secure server network listener. |
| // either Listener or BindAddress/BindPort/BindNetwork is set, |
| // if Listener is set, use it and omit BindAddress/BindPort/BindNetwork. |
| Listener net.Listener |
| |
| // ServerCert is the TLS cert info for serving secure traffic |
| ServerCert GeneratableKeyCert |
| // SNICertKeys are named CertKeys for serving secure traffic with SNI support. |
| SNICertKeys []utilflag.NamedCertKey |
| // CipherSuites is the list of allowed cipher suites for the server. |
| // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). |
| CipherSuites []string |
| // MinTLSVersion is the minimum TLS version supported. |
| // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). |
| MinTLSVersion string |
| |
| // HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client. |
| // A value of zero means to use the default provided by golang's HTTP/2 support. |
| HTTP2MaxStreamsPerConnection int |
| } |
| |
| type CertKey struct { |
| // CertFile is a file containing a PEM-encoded certificate, and possibly the complete certificate chain |
| CertFile string |
| // KeyFile is a file containing a PEM-encoded private key for the certificate specified by CertFile |
| KeyFile string |
| } |
| |
| type GeneratableKeyCert struct { |
| CertKey CertKey |
| |
| // CertDirectory is a directory that will contain the certificates. If the cert and key aren't specifically set |
| // this will be used to derive a match with the "pair-name" |
| CertDirectory string |
| // PairName is the name which will be used with CertDirectory to make a cert and key names |
| // It becomes CertDirector/PairName.crt and CertDirector/PairName.key |
| PairName string |
| } |
| |
| func NewSecureServingOptions() *SecureServingOptions { |
| return &SecureServingOptions{ |
| BindAddress: net.ParseIP("0.0.0.0"), |
| BindPort: 443, |
| ServerCert: GeneratableKeyCert{ |
| PairName: "apiserver", |
| CertDirectory: "apiserver.local.config/certificates", |
| }, |
| } |
| } |
| |
| func (s *SecureServingOptions) DefaultExternalAddress() (net.IP, error) { |
| return utilnet.ChooseBindAddress(s.BindAddress) |
| } |
| |
| func (s *SecureServingOptions) Validate() []error { |
| if s == nil { |
| return nil |
| } |
| |
| errors := []error{} |
| |
| if s.BindPort < 0 || s.BindPort > 65535 { |
| errors = append(errors, fmt.Errorf("--secure-port %v must be between 0 and 65535, inclusive. 0 for turning off secure port", s.BindPort)) |
| } |
| |
| return errors |
| } |
| |
| func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) { |
| if s == nil { |
| return |
| } |
| |
| fs.IPVar(&s.BindAddress, "bind-address", s.BindAddress, ""+ |
| "The IP address on which to listen for the --secure-port port. The "+ |
| "associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+ |
| "clients. If blank, all interfaces will be used (0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).") |
| fs.IntVar(&s.BindPort, "secure-port", s.BindPort, ""+ |
| "The port on which to serve HTTPS with authentication and authorization. If 0, "+ |
| "don't serve HTTPS at all.") |
| |
| fs.StringVar(&s.ServerCert.CertDirectory, "cert-dir", s.ServerCert.CertDirectory, ""+ |
| "The directory where the TLS certs are located. "+ |
| "If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.") |
| |
| fs.StringVar(&s.ServerCert.CertKey.CertFile, "tls-cert-file", s.ServerCert.CertKey.CertFile, ""+ |
| "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated "+ |
| "after server cert). If HTTPS serving is enabled, and --tls-cert-file and "+ |
| "--tls-private-key-file are not provided, a self-signed certificate and key "+ |
| "are generated for the public address and saved to the directory specified by --cert-dir.") |
| |
| fs.StringVar(&s.ServerCert.CertKey.KeyFile, "tls-private-key-file", s.ServerCert.CertKey.KeyFile, |
| "File containing the default x509 private key matching --tls-cert-file.") |
| |
| tlsCipherPossibleValues := utilflag.TLSCipherPossibleValues() |
| fs.StringSliceVar(&s.CipherSuites, "tls-cipher-suites", s.CipherSuites, |
| "Comma-separated list of cipher suites for the server. "+ |
| "If omitted, the default Go cipher suites will be use. "+ |
| "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) |
| |
| tlsPossibleVersions := utilflag.TLSPossibleVersions() |
| fs.StringVar(&s.MinTLSVersion, "tls-min-version", s.MinTLSVersion, |
| "Minimum TLS version supported. "+ |
| "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) |
| |
| fs.Var(utilflag.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+ |
| "A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+ |
| "domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+ |
| "segments. If no domain patterns are provided, the names of the certificate are "+ |
| "extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns "+ |
| "trump over extracted names. For multiple key/certificate pairs, use the "+ |
| "--tls-sni-cert-key multiple times. "+ |
| "Examples: \"example.crt,example.key\" or \"foo.crt,foo.key:*.foo.com,foo.com\".") |
| |
| fs.IntVar(&s.HTTP2MaxStreamsPerConnection, "http2-max-streams-per-connection", s.HTTP2MaxStreamsPerConnection, ""+ |
| "The limit that the server gives to clients for "+ |
| "the maximum number of streams in an HTTP/2 connection. "+ |
| "Zero means to use golang's default.") |
| } |
| |
| // ApplyTo fills up serving information in the server configuration. |
| func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error { |
| if s == nil { |
| return nil |
| } |
| if s.BindPort <= 0 && s.Listener == nil { |
| return nil |
| } |
| |
| if s.Listener == nil { |
| var err error |
| addr := net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort)) |
| s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr) |
| if err != nil { |
| return fmt.Errorf("failed to create listener: %v", err) |
| } |
| } else { |
| if _, ok := s.Listener.Addr().(*net.TCPAddr); !ok { |
| return fmt.Errorf("failed to parse ip and port from listener") |
| } |
| s.BindPort = s.Listener.Addr().(*net.TCPAddr).Port |
| s.BindAddress = s.Listener.Addr().(*net.TCPAddr).IP |
| } |
| |
| *config = &server.SecureServingInfo{ |
| Listener: s.Listener, |
| HTTP2MaxStreamsPerConnection: s.HTTP2MaxStreamsPerConnection, |
| } |
| c := *config |
| |
| serverCertFile, serverKeyFile := s.ServerCert.CertKey.CertFile, s.ServerCert.CertKey.KeyFile |
| // load main cert |
| if len(serverCertFile) != 0 || len(serverKeyFile) != 0 { |
| tlsCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile) |
| if err != nil { |
| return fmt.Errorf("unable to load server certificate: %v", err) |
| } |
| c.Cert = &tlsCert |
| } |
| |
| if len(s.CipherSuites) != 0 { |
| cipherSuites, err := utilflag.TLSCipherSuites(s.CipherSuites) |
| if err != nil { |
| return err |
| } |
| c.CipherSuites = cipherSuites |
| } |
| |
| var err error |
| c.MinTLSVersion, err = utilflag.TLSVersion(s.MinTLSVersion) |
| if err != nil { |
| return err |
| } |
| |
| // load SNI certs |
| namedTLSCerts := make([]server.NamedTLSCert, 0, len(s.SNICertKeys)) |
| for _, nck := range s.SNICertKeys { |
| tlsCert, err := tls.LoadX509KeyPair(nck.CertFile, nck.KeyFile) |
| namedTLSCerts = append(namedTLSCerts, server.NamedTLSCert{ |
| TLSCert: tlsCert, |
| Names: nck.Names, |
| }) |
| if err != nil { |
| return fmt.Errorf("failed to load SNI cert and key: %v", err) |
| } |
| } |
| c.SNICerts, err = server.GetNamedCertificateMap(namedTLSCerts) |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (s *SecureServingOptions) MaybeDefaultWithSelfSignedCerts(publicAddress string, alternateDNS []string, alternateIPs []net.IP) error { |
| if s == nil { |
| return nil |
| } |
| keyCert := &s.ServerCert.CertKey |
| if len(keyCert.CertFile) != 0 || len(keyCert.KeyFile) != 0 { |
| return nil |
| } |
| |
| keyCert.CertFile = path.Join(s.ServerCert.CertDirectory, s.ServerCert.PairName+".crt") |
| keyCert.KeyFile = path.Join(s.ServerCert.CertDirectory, s.ServerCert.PairName+".key") |
| |
| canReadCertAndKey, err := certutil.CanReadCertAndKey(keyCert.CertFile, keyCert.KeyFile) |
| if err != nil { |
| return err |
| } |
| if !canReadCertAndKey { |
| // add either the bind address or localhost to the valid alternates |
| bindIP := s.BindAddress.String() |
| if bindIP == "0.0.0.0" { |
| alternateDNS = append(alternateDNS, "localhost") |
| } else { |
| alternateIPs = append(alternateIPs, s.BindAddress) |
| } |
| |
| if cert, key, err := certutil.GenerateSelfSignedCertKey(publicAddress, alternateIPs, alternateDNS); err != nil { |
| return fmt.Errorf("unable to generate self signed cert: %v", err) |
| } else { |
| if err := certutil.WriteCert(keyCert.CertFile, cert); err != nil { |
| return err |
| } |
| |
| if err := certutil.WriteKey(keyCert.KeyFile, key); err != nil { |
| return err |
| } |
| glog.Infof("Generated self-signed cert (%s, %s)", keyCert.CertFile, keyCert.KeyFile) |
| } |
| } |
| |
| return nil |
| } |
| |
| func CreateListener(network, addr string) (net.Listener, int, error) { |
| if len(network) == 0 { |
| network = "tcp" |
| } |
| ln, err := net.Listen(network, addr) |
| if err != nil { |
| return nil, 0, fmt.Errorf("failed to listen on %v: %v", addr, err) |
| } |
| |
| // get port |
| tcpAddr, ok := ln.Addr().(*net.TCPAddr) |
| if !ok { |
| ln.Close() |
| return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String()) |
| } |
| |
| return ln, tcpAddr.Port, nil |
| } |