forked from github/dynamiclistener
Initial Commit
This commit is contained in:
commit
077eb13a90
178
LICENSE
Normal file
178
LICENSE
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
269
cert/cert.go
Normal file
269
cert/cert.go
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
Copyright 2014 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 cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
rsaKeySize = 2048
|
||||
duration365d = time.Hour * 24 * 365
|
||||
)
|
||||
|
||||
// Config contains the basic fields required for creating a certificate
|
||||
type Config struct {
|
||||
CommonName string
|
||||
Organization []string
|
||||
AltNames AltNames
|
||||
Usages []x509.ExtKeyUsage
|
||||
}
|
||||
|
||||
// AltNames contains the domain names and IP addresses that will be added
|
||||
// to the API Server's x509 certificate SubAltNames field. The values will
|
||||
// be passed directly to the x509.Certificate object.
|
||||
type AltNames struct {
|
||||
DNSNames []string
|
||||
IPs []net.IP
|
||||
}
|
||||
|
||||
// NewPrivateKey creates an RSA private key
|
||||
func NewPrivateKey() (*rsa.PrivateKey, error) {
|
||||
return rsa.GenerateKey(cryptorand.Reader, rsaKeySize)
|
||||
}
|
||||
|
||||
// NewSelfSignedCACert creates a CA certificate
|
||||
func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
|
||||
now := time.Now()
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: new(big.Int).SetInt64(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: cfg.CommonName,
|
||||
Organization: cfg.Organization,
|
||||
},
|
||||
NotBefore: now.UTC(),
|
||||
NotAfter: now.Add(duration365d * 10).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParseCertificate(certDERBytes)
|
||||
}
|
||||
|
||||
// NewSignedCert creates a signed certificate using the given CA certificate and key
|
||||
func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cfg.CommonName) == 0 {
|
||||
return nil, errors.New("must specify a CommonName")
|
||||
}
|
||||
if len(cfg.Usages) == 0 {
|
||||
return nil, errors.New("must specify at least one ExtKeyUsage")
|
||||
}
|
||||
|
||||
certTmpl := x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: cfg.CommonName,
|
||||
Organization: cfg.Organization,
|
||||
},
|
||||
DNSNames: cfg.AltNames.DNSNames,
|
||||
IPAddresses: cfg.AltNames.IPs,
|
||||
SerialNumber: serial,
|
||||
NotBefore: caCert.NotBefore,
|
||||
NotAfter: time.Now().Add(duration365d).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: cfg.Usages,
|
||||
}
|
||||
certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &certTmpl, caCert, key.Public(), caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParseCertificate(certDERBytes)
|
||||
}
|
||||
|
||||
// MakeEllipticPrivateKeyPEM creates an ECDSA private key
|
||||
func MakeEllipticPrivateKeyPEM() ([]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: ECPrivateKeyBlockType,
|
||||
Bytes: derBytes,
|
||||
}
|
||||
return pem.EncodeToMemory(privateKeyPemBlock), nil
|
||||
}
|
||||
|
||||
// GenerateSelfSignedCertKey creates a self-signed certificate and key for the given host.
|
||||
// Host may be an IP or a DNS name
|
||||
// You may also specify additional subject alt names (either ip or dns names) for the certificate.
|
||||
func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) {
|
||||
return GenerateSelfSignedCertKeyWithFixtures(host, alternateIPs, alternateDNS, "")
|
||||
}
|
||||
|
||||
// GenerateSelfSignedCertKeyWithFixtures creates a self-signed certificate and key for the given host.
|
||||
// Host may be an IP or a DNS name. You may also specify additional subject alt names (either ip or dns names)
|
||||
// for the certificate.
|
||||
//
|
||||
// If fixtureDirectory is non-empty, it is a directory path which can contain pre-generated certs. The format is:
|
||||
// <host>_<ip>-<ip>_<alternateDNS>-<alternateDNS>.crt
|
||||
// <host>_<ip>-<ip>_<alternateDNS>-<alternateDNS>.key
|
||||
// Certs/keys not existing in that directory are created.
|
||||
func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, alternateDNS []string, fixtureDirectory string) ([]byte, []byte, error) {
|
||||
validFrom := time.Now().Add(-time.Hour) // valid an hour earlier to avoid flakes due to clock skew
|
||||
maxAge := time.Hour * 24 * 365 // one year self-signed certs
|
||||
|
||||
baseName := fmt.Sprintf("%s_%s_%s", host, strings.Join(ipsToStrings(alternateIPs), "-"), strings.Join(alternateDNS, "-"))
|
||||
certFixturePath := path.Join(fixtureDirectory, baseName+".crt")
|
||||
keyFixturePath := path.Join(fixtureDirectory, baseName+".key")
|
||||
if len(fixtureDirectory) > 0 {
|
||||
cert, err := ioutil.ReadFile(certFixturePath)
|
||||
if err == nil {
|
||||
key, err := ioutil.ReadFile(keyFixturePath)
|
||||
if err == nil {
|
||||
return cert, key, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("cert %s can be read, but key %s cannot: %v", certFixturePath, keyFixturePath, err)
|
||||
}
|
||||
maxAge = 100 * time.Hour * 24 * 365 // 100 years fixtures
|
||||
}
|
||||
|
||||
caKey, err := rsa.GenerateKey(cryptorand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caTemplate := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: fmt.Sprintf("%s-ca@%d", host, time.Now().Unix()),
|
||||
},
|
||||
NotBefore: validFrom,
|
||||
NotAfter: validFrom.Add(maxAge),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
caDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCertificate, err := x509.ParseCertificate(caDERBytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
Subject: pkix.Name{
|
||||
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
|
||||
},
|
||||
NotBefore: validFrom,
|
||||
NotAfter: validFrom.Add(maxAge),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, host)
|
||||
}
|
||||
|
||||
template.IPAddresses = append(template.IPAddresses, alternateIPs...)
|
||||
template.DNSNames = append(template.DNSNames, alternateDNS...)
|
||||
|
||||
derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, caCertificate, &priv.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Generate cert, followed by ca
|
||||
certBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&certBuffer, &pem.Block{Type: CertificateBlockType, Bytes: derBytes}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := pem.Encode(&certBuffer, &pem.Block{Type: CertificateBlockType, Bytes: caDERBytes}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Generate key
|
||||
keyBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&keyBuffer, &pem.Block{Type: RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(fixtureDirectory) > 0 {
|
||||
if err := ioutil.WriteFile(certFixturePath, certBuffer.Bytes(), 0644); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to write cert fixture to %s: %v", certFixturePath, err)
|
||||
}
|
||||
if err := ioutil.WriteFile(keyFixturePath, keyBuffer.Bytes(), 0644); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to write key fixture to %s: %v", certFixturePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return certBuffer.Bytes(), keyBuffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func ipsToStrings(ips []net.IP) []string {
|
||||
ss := make([]string, 0, len(ips))
|
||||
for _, ip := range ips {
|
||||
ss = append(ss, ip.String())
|
||||
}
|
||||
return ss
|
||||
}
|
75
cert/csr.go
Normal file
75
cert/csr.go
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 cert
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
)
|
||||
|
||||
// MakeCSR generates a PEM-encoded CSR using the supplied private key, subject, and SANs.
|
||||
// All key types that are implemented via crypto.Signer are supported (This includes *rsa.PrivateKey and *ecdsa.PrivateKey.)
|
||||
func MakeCSR(privateKey interface{}, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, err error) {
|
||||
template := &x509.CertificateRequest{
|
||||
Subject: *subject,
|
||||
DNSNames: dnsSANs,
|
||||
IPAddresses: ipSANs,
|
||||
}
|
||||
|
||||
return MakeCSRFromTemplate(privateKey, template)
|
||||
}
|
||||
|
||||
// MakeCSRFromTemplate generates a PEM-encoded CSR using the supplied private
|
||||
// key and certificate request as a template. All key types that are
|
||||
// implemented via crypto.Signer are supported (This includes *rsa.PrivateKey
|
||||
// and *ecdsa.PrivateKey.)
|
||||
func MakeCSRFromTemplate(privateKey interface{}, template *x509.CertificateRequest) ([]byte, error) {
|
||||
t := *template
|
||||
t.SignatureAlgorithm = sigType(privateKey)
|
||||
|
||||
csrDER, err := x509.CreateCertificateRequest(cryptorand.Reader, &t, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
csrPemBlock := &pem.Block{
|
||||
Type: CertificateRequestBlockType,
|
||||
Bytes: csrDER,
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(csrPemBlock), nil
|
||||
}
|
||||
|
||||
func sigType(privateKey interface{}) x509.SignatureAlgorithm {
|
||||
// Customize the signature for RSA keys, depending on the key size
|
||||
if privateKey, ok := privateKey.(*rsa.PrivateKey); ok {
|
||||
keySize := privateKey.N.BitLen()
|
||||
switch {
|
||||
case keySize >= 4096:
|
||||
return x509.SHA512WithRSA
|
||||
case keySize >= 3072:
|
||||
return x509.SHA384WithRSA
|
||||
default:
|
||||
return x509.SHA256WithRSA
|
||||
}
|
||||
}
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
193
cert/io.go
Normal file
193
cert/io.go
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
Copyright 2014 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 cert
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CanReadCertAndKey returns true if the certificate and key files already exists,
|
||||
// otherwise returns false. If lost one of cert and key, returns error.
|
||||
func CanReadCertAndKey(certPath, keyPath string) (bool, error) {
|
||||
certReadable := canReadFile(certPath)
|
||||
keyReadable := canReadFile(keyPath)
|
||||
|
||||
if certReadable == false && keyReadable == false {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if certReadable == false {
|
||||
return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", certPath)
|
||||
}
|
||||
|
||||
if keyReadable == false {
|
||||
return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", keyPath)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// If the file represented by path exists and
|
||||
// readable, returns true otherwise returns false.
|
||||
func canReadFile(path string) bool {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteCert 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 WriteCert(certPath string, data []byte) error {
|
||||
if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(certPath, data, os.FileMode(0644))
|
||||
}
|
||||
|
||||
// WriteKey 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 WriteKey(keyPath string, data []byte) error {
|
||||
if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(keyPath, data, os.FileMode(0600))
|
||||
}
|
||||
|
||||
// LoadOrGenerateKeyFile looks for a key in the file at the given path. If it
|
||||
// can't find one, it will generate a new key and store it there.
|
||||
func LoadOrGenerateKeyFile(keyPath string) (data []byte, wasGenerated bool, err error) {
|
||||
loadedData, err := ioutil.ReadFile(keyPath)
|
||||
// Call verifyKeyData to ensure the file wasn't empty/corrupt.
|
||||
if err == nil && verifyKeyData(loadedData) {
|
||||
return loadedData, false, err
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, false, fmt.Errorf("error loading key from %s: %v", keyPath, err)
|
||||
}
|
||||
|
||||
generatedData, err := MakeEllipticPrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error generating key: %v", err)
|
||||
}
|
||||
if err := WriteKey(keyPath, generatedData); err != nil {
|
||||
return nil, false, fmt.Errorf("error writing key to %s: %v", keyPath, err)
|
||||
}
|
||||
return generatedData, true, nil
|
||||
}
|
||||
|
||||
// MarshalPrivateKeyToPEM converts a known private key type of RSA or ECDSA to
|
||||
// a PEM encoded block or returns an error.
|
||||
func MarshalPrivateKeyToPEM(privateKey crypto.PrivateKey) ([]byte, error) {
|
||||
switch t := privateKey.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
derBytes, err := x509.MarshalECPrivateKey(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKeyPemBlock := &pem.Block{
|
||||
Type: ECPrivateKeyBlockType,
|
||||
Bytes: derBytes,
|
||||
}
|
||||
return pem.EncodeToMemory(privateKeyPemBlock), nil
|
||||
case *rsa.PrivateKey:
|
||||
return EncodePrivateKeyPEM(t), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("private key is not a recognized type: %T", privateKey)
|
||||
}
|
||||
}
|
||||
|
||||
// NewPool 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 NewPool(filename string) (*x509.CertPool, error) {
|
||||
certs, err := CertsFromFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
for _, cert := range certs {
|
||||
pool.AddCert(cert)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// CertsFromFile returns the x509.Certificates contained 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 CertsFromFile(file string) ([]*x509.Certificate, error) {
|
||||
pemBlock, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs, err := ParseCertsPEM(pemBlock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", file, err)
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromFile returns the private key in rsa.PrivateKey or ecdsa.PrivateKey format from a given PEM-encoded file.
|
||||
// Returns an error if the file could not be read or if the private key could not be parsed.
|
||||
func PrivateKeyFromFile(file string) (interface{}, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := ParsePrivateKeyPEM(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading private key file %s: %v", file, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// PublicKeysFromFile returns the public keys in rsa.PublicKey or ecdsa.PublicKey format from a given PEM-encoded file.
|
||||
// Reads public keys from both public and private key files.
|
||||
func PublicKeysFromFile(file string) ([]interface{}, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err := ParsePublicKeysPEM(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading public key file %s: %v", file, err)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// verifyKeyData returns true if the provided data appears to be a valid private key.
|
||||
func verifyKeyData(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
_, err := ParsePrivateKeyPEM(data)
|
||||
return err == nil
|
||||
}
|
269
cert/pem.go
Normal file
269
cert/pem.go
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
Copyright 2014 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 cert
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// ECPrivateKeyBlockType is a possible value for pem.Block.Type.
|
||||
ECPrivateKeyBlockType = "EC PRIVATE KEY"
|
||||
// RSAPrivateKeyBlockType is a possible value for pem.Block.Type.
|
||||
RSAPrivateKeyBlockType = "RSA PRIVATE KEY"
|
||||
// PrivateKeyBlockType is a possible value for pem.Block.Type.
|
||||
PrivateKeyBlockType = "PRIVATE KEY"
|
||||
// PublicKeyBlockType is a possible value for pem.Block.Type.
|
||||
PublicKeyBlockType = "PUBLIC KEY"
|
||||
// CertificateBlockType is a possible value for pem.Block.Type.
|
||||
CertificateBlockType = "CERTIFICATE"
|
||||
// CertificateRequestBlockType is a possible value for pem.Block.Type.
|
||||
CertificateRequestBlockType = "CERTIFICATE REQUEST"
|
||||
)
|
||||
|
||||
// EncodePublicKeyPEM returns PEM-encoded public data
|
||||
func EncodePublicKeyPEM(key *rsa.PublicKey) ([]byte, error) {
|
||||
der, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
block := pem.Block{
|
||||
Type: PublicKeyBlockType,
|
||||
Bytes: der,
|
||||
}
|
||||
return pem.EncodeToMemory(&block), nil
|
||||
}
|
||||
|
||||
// EncodePrivateKeyPEM returns PEM-encoded private key data
|
||||
func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
|
||||
block := pem.Block{
|
||||
Type: RSAPrivateKeyBlockType,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}
|
||||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
// EncodeCertPEM returns PEM-endcoded certificate data
|
||||
func EncodeCertPEM(cert *x509.Certificate) []byte {
|
||||
block := pem.Block{
|
||||
Type: CertificateBlockType,
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
// ParsePrivateKeyPEM returns a private key parsed from a PEM block in the supplied data.
|
||||
// Recognizes PEM blocks for "EC PRIVATE KEY", "RSA PRIVATE KEY", or "PRIVATE KEY"
|
||||
func ParsePrivateKeyPEM(keyData []byte) (interface{}, error) {
|
||||
var privateKeyPemBlock *pem.Block
|
||||
for {
|
||||
privateKeyPemBlock, keyData = pem.Decode(keyData)
|
||||
if privateKeyPemBlock == nil {
|
||||
break
|
||||
}
|
||||
|
||||
switch privateKeyPemBlock.Type {
|
||||
case ECPrivateKeyBlockType:
|
||||
// ECDSA Private Key in ASN.1 format
|
||||
if key, err := x509.ParseECPrivateKey(privateKeyPemBlock.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
case RSAPrivateKeyBlockType:
|
||||
// RSA Private Key in PKCS#1 format
|
||||
if key, err := x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
case PrivateKeyBlockType:
|
||||
// RSA or ECDSA Private Key in unencrypted PKCS#8 format
|
||||
if key, err := x509.ParsePKCS8PrivateKey(privateKeyPemBlock.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
|
||||
// tolerate non-key PEM blocks for compatibility with things like "EC PARAMETERS" blocks
|
||||
// originally, only the first PEM block was parsed and expected to be a key block
|
||||
}
|
||||
|
||||
// we read all the PEM blocks and didn't recognize one
|
||||
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA private key")
|
||||
}
|
||||
|
||||
// ParsePublicKeysPEM is a helper function for reading an array of rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded byte array.
|
||||
// Reads public keys from both public and private key files.
|
||||
func ParsePublicKeysPEM(keyData []byte) ([]interface{}, error) {
|
||||
var block *pem.Block
|
||||
keys := []interface{}{}
|
||||
for {
|
||||
// read the next block
|
||||
block, keyData = pem.Decode(keyData)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// test block against parsing functions
|
||||
if privateKey, err := parseRSAPrivateKey(block.Bytes); err == nil {
|
||||
keys = append(keys, &privateKey.PublicKey)
|
||||
continue
|
||||
}
|
||||
if publicKey, err := parseRSAPublicKey(block.Bytes); err == nil {
|
||||
keys = append(keys, publicKey)
|
||||
continue
|
||||
}
|
||||
if privateKey, err := parseECPrivateKey(block.Bytes); err == nil {
|
||||
keys = append(keys, &privateKey.PublicKey)
|
||||
continue
|
||||
}
|
||||
if publicKey, err := parseECPublicKey(block.Bytes); err == nil {
|
||||
keys = append(keys, publicKey)
|
||||
continue
|
||||
}
|
||||
|
||||
// tolerate non-key PEM blocks for backwards compatibility
|
||||
// originally, only the first PEM block was parsed and expected to be a key block
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return nil, fmt.Errorf("data does not contain any valid RSA or ECDSA public keys")
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// ParseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array
|
||||
// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates
|
||||
func ParseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) {
|
||||
ok := false
|
||||
certs := []*x509.Certificate{}
|
||||
for len(pemCerts) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemCerts = pem.Decode(pemCerts)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
// Only use PEM "CERTIFICATE" blocks without extra headers
|
||||
if block.Type != CertificateBlockType || len(block.Headers) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return certs, err
|
||||
}
|
||||
|
||||
certs = append(certs, cert)
|
||||
ok = true
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return certs, errors.New("data does not contain any valid RSA or ECDSA certificates")
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// parseRSAPublicKey parses a single RSA public key from the provided data
|
||||
func parseRSAPublicKey(data []byte) (*rsa.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKIXPublicKey(data); err != nil {
|
||||
if cert, err := x509.ParseCertificate(data); err == nil {
|
||||
parsedKey = cert.PublicKey
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Test if parsed key is an RSA Public Key
|
||||
var pubKey *rsa.PublicKey
|
||||
var ok bool
|
||||
if pubKey, ok = parsedKey.(*rsa.PublicKey); !ok {
|
||||
return nil, fmt.Errorf("data doesn't contain valid RSA Public Key")
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// parseRSAPrivateKey parses a single RSA private key from the provided data
|
||||
func parseRSAPrivateKey(data []byte) (*rsa.PrivateKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKCS1PrivateKey(data); err != nil {
|
||||
if parsedKey, err = x509.ParsePKCS8PrivateKey(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Test if parsed key is an RSA Private Key
|
||||
var privKey *rsa.PrivateKey
|
||||
var ok bool
|
||||
if privKey, ok = parsedKey.(*rsa.PrivateKey); !ok {
|
||||
return nil, fmt.Errorf("data doesn't contain valid RSA Private Key")
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// parseECPublicKey parses a single ECDSA public key from the provided data
|
||||
func parseECPublicKey(data []byte) (*ecdsa.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKIXPublicKey(data); err != nil {
|
||||
if cert, err := x509.ParseCertificate(data); err == nil {
|
||||
parsedKey = cert.PublicKey
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Test if parsed key is an ECDSA Public Key
|
||||
var pubKey *ecdsa.PublicKey
|
||||
var ok bool
|
||||
if pubKey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
|
||||
return nil, fmt.Errorf("data doesn't contain valid ECDSA Public Key")
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// parseECPrivateKey parses a single ECDSA private key from the provided data
|
||||
func parseECPrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParseECPrivateKey(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Test if parsed key is an ECDSA Private Key
|
||||
var privKey *ecdsa.PrivateKey
|
||||
var ok bool
|
||||
if privKey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
|
||||
return nil, fmt.Errorf("data doesn't contain valid ECDSA Private Key")
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module github.com/rancher/dynamiclistener
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/hashicorp/golang-lru v0.5.1
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
38
go.sum
Normal file
38
go.sum
Normal file
@ -0,0 +1,38 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
55
read.go
Normal file
55
read.go
Normal file
@ -0,0 +1,55 @@
|
||||
package dynamiclistener
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ReadTLSConfig(userConfig *UserConfig) error {
|
||||
var err error
|
||||
|
||||
path := userConfig.CertPath
|
||||
|
||||
userConfig.CACerts, err = readPEM(filepath.Join(path, "cacerts.pem"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userConfig.Key, err = readPEM(filepath.Join(path, "key.pem"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userConfig.Cert, err = readPEM(filepath.Join(path, "cert.pem"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userConfig.Mode = "https"
|
||||
if len(userConfig.Domains) > 0 {
|
||||
userConfig.Mode = "acme"
|
||||
}
|
||||
|
||||
valid := false
|
||||
if userConfig.Key != "" && userConfig.Cert != "" {
|
||||
valid = true
|
||||
} else if userConfig.Key == "" && userConfig.Cert == "" {
|
||||
valid = true
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return fmt.Errorf("invalid SSL configuration found, please set cert/key, cert/key/cacerts, cacerts only, or none")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPEM(path string) (string, error) {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return string(content), nil
|
||||
}
|
645
server.go
Normal file
645
server.go
Normal file
@ -0,0 +1,645 @@
|
||||
package dynamiclistener
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
cert "github.com/rancher/dynamiclistener/cert"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
const (
|
||||
httpsMode = "https"
|
||||
acmeMode = "acme"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
sync.Mutex
|
||||
|
||||
userConfig UserConfig
|
||||
listenConfigStorage ListenerConfigStorage
|
||||
certs map[string]*tls.Certificate
|
||||
ips *lru.Cache
|
||||
|
||||
listeners []net.Listener
|
||||
servers []*http.Server
|
||||
|
||||
// dynamic config change on refresh
|
||||
activeCert *tls.Certificate
|
||||
activeCA *x509.Certificate
|
||||
activeCAKey *rsa.PrivateKey
|
||||
activeCAKeyString string
|
||||
domains map[string]bool
|
||||
}
|
||||
|
||||
func NewServer(listenConfigStorage ListenerConfigStorage, config UserConfig) (ServerInterface, error) {
|
||||
s := &server{
|
||||
userConfig: config,
|
||||
listenConfigStorage: listenConfigStorage,
|
||||
certs: map[string]*tls.Certificate{},
|
||||
}
|
||||
|
||||
s.ips, _ = lru.New(20)
|
||||
|
||||
if err := s.userConfigure(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lc, err := listenConfigStorage.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, s.Update(lc)
|
||||
}
|
||||
|
||||
func (s *server) CACert() (string, error) {
|
||||
if s.userConfig.NoCACerts {
|
||||
return "", nil
|
||||
}
|
||||
if s.userConfig.CACerts != "" {
|
||||
return s.userConfig.CACerts, nil
|
||||
}
|
||||
status, err := s.listenConfigStorage.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if status.CACert == "" {
|
||||
return "", fmt.Errorf("ca cert not found")
|
||||
}
|
||||
|
||||
return status.CACert, nil
|
||||
}
|
||||
|
||||
func (s *server) save() {
|
||||
if s.activeCert != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
changed := false
|
||||
cfg, err := s.listenConfigStorage.Get()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.GeneratedCerts == nil {
|
||||
cfg.GeneratedCerts = map[string]string{}
|
||||
}
|
||||
|
||||
if cfg.KnownIPs == nil {
|
||||
cfg.KnownIPs = map[string]bool{}
|
||||
}
|
||||
|
||||
for key, cert := range s.certs {
|
||||
certStr := certToString(cert)
|
||||
if cfg.GeneratedCerts[key] != certStr {
|
||||
cfg.GeneratedCerts[key] = certStr
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, obj := range s.ips.Keys() {
|
||||
ip, _ := obj.(string)
|
||||
if !cfg.KnownIPs[ip] {
|
||||
cfg.KnownIPs[ip] = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.CAKey == "" && s.activeCAKey != nil && s.activeCA != nil {
|
||||
caCertBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&caCertBuffer, &pem.Block{
|
||||
Type: cert.CertificateBlockType,
|
||||
Bytes: s.activeCA.Raw,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
caKeyBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&caKeyBuffer, &pem.Block{
|
||||
Type: cert.RSAPrivateKeyBlockType,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(s.activeCAKey),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.CACert = string(caCertBuffer.Bytes())
|
||||
cfg.CAKey = string(caKeyBuffer.Bytes())
|
||||
s.activeCAKeyString = cfg.CAKey
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed {
|
||||
s.listenConfigStorage.Set(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) userConfigure() error {
|
||||
if s.userConfig.HTTPSPort == 0 {
|
||||
s.userConfig.HTTPSPort = 8443
|
||||
}
|
||||
|
||||
if s.userConfig.Mode == "" {
|
||||
if len(s.userConfig.Domains) > 0 {
|
||||
s.userConfig.Mode = acmeMode
|
||||
} else {
|
||||
s.userConfig.Mode = httpsMode
|
||||
}
|
||||
}
|
||||
|
||||
s.domains = map[string]bool{}
|
||||
for _, d := range s.userConfig.Domains {
|
||||
s.domains[d] = true
|
||||
}
|
||||
|
||||
if s.userConfig.Key != "" && s.userConfig.Cert != "" {
|
||||
cert, err := tls.X509KeyPair([]byte(s.userConfig.Cert), []byte(s.userConfig.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.activeCert = &cert
|
||||
s.userConfig.Mode = httpsMode
|
||||
return s.reload()
|
||||
}
|
||||
|
||||
for _, ip := range s.userConfig.KnownIPs {
|
||||
netIP := net.ParseIP(ip)
|
||||
if netIP != nil {
|
||||
s.ips.Add(ip, netIP)
|
||||
}
|
||||
}
|
||||
bindAddress := net.ParseIP(s.userConfig.BindAddress)
|
||||
if bindAddress != nil {
|
||||
s.ips.Add(s.userConfig.BindAddress, bindAddress)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func genCA() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
caKey, err := cert.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCert, err := cert.NewSelfSignedCACert(cert.Config{
|
||||
CommonName: "k3s-ca",
|
||||
Organization: []string{"k3s-org"},
|
||||
}, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return caCert, caKey, nil
|
||||
}
|
||||
|
||||
func (s *server) Update(status *ListenerStatus) error {
|
||||
s.Lock()
|
||||
defer s.getCertificate(&tls.ClientHelloInfo{ServerName: "localhost"})
|
||||
|
||||
if status.CACert != "" && status.CAKey != "" && s.activeCAKeyString != status.CAKey {
|
||||
cert, err := tls.X509KeyPair([]byte(status.CACert), []byte(status.CAKey))
|
||||
if err != nil {
|
||||
s.Unlock()
|
||||
return err
|
||||
}
|
||||
s.activeCAKey = cert.PrivateKey.(*rsa.PrivateKey)
|
||||
s.activeCAKeyString = status.CAKey
|
||||
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
s.Unlock()
|
||||
return err
|
||||
}
|
||||
s.activeCA = x509Cert
|
||||
s.certs = map[string]*tls.Certificate{}
|
||||
}
|
||||
|
||||
for ipStr := range status.KnownIPs {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if len(ip) > 0 {
|
||||
s.ips.ContainsOrAdd(ipStr, ip)
|
||||
}
|
||||
}
|
||||
|
||||
for key, certString := range status.GeneratedCerts {
|
||||
cert := stringToCert(certString)
|
||||
if cert != nil {
|
||||
s.certs[key] = cert
|
||||
}
|
||||
}
|
||||
|
||||
s.Unlock()
|
||||
return s.reload()
|
||||
}
|
||||
|
||||
func (s *server) hostPolicy(ctx context.Context, host string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.domains[host] {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("acme/autocert: host not configured")
|
||||
}
|
||||
|
||||
func (s *server) prompt(tos string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *server) shutdown() error {
|
||||
for _, listener := range s.listeners {
|
||||
if err := listener.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.listeners = nil
|
||||
|
||||
for _, server := range s.servers {
|
||||
go server.Shutdown(context.Background())
|
||||
}
|
||||
s.servers = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) reload() error {
|
||||
if len(s.listeners) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch s.userConfig.Mode {
|
||||
case acmeMode:
|
||||
if err := s.serveACME(); err != nil {
|
||||
return err
|
||||
}
|
||||
case httpsMode:
|
||||
if err := s.serveHTTPS(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) ipMapKey() string {
|
||||
len := s.ips.Len()
|
||||
keys := s.ips.Keys()
|
||||
if len == 0 {
|
||||
return fmt.Sprintf("local/%d", len)
|
||||
} else if len == 1 {
|
||||
return fmt.Sprintf("local/%s", keys[0])
|
||||
}
|
||||
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
l, _ := keys[i].(string)
|
||||
r, _ := keys[j].(string)
|
||||
return l < r
|
||||
})
|
||||
if len < 6 {
|
||||
return fmt.Sprintf("local/%v", keys)
|
||||
}
|
||||
|
||||
digest := md5.New()
|
||||
for _, k := range keys {
|
||||
s, _ := k.(string)
|
||||
digest.Write([]byte(s))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("local/%v", hex.EncodeToString(digest.Sum(nil)))
|
||||
}
|
||||
|
||||
func (s *server) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
s.Lock()
|
||||
if s.activeCert != nil {
|
||||
s.Unlock()
|
||||
return s.activeCert, nil
|
||||
}
|
||||
|
||||
changed := false
|
||||
defer func() {
|
||||
if changed {
|
||||
s.save()
|
||||
}
|
||||
}()
|
||||
defer s.Unlock()
|
||||
|
||||
mapKey := hello.ServerName
|
||||
cn := hello.ServerName
|
||||
dnsNames := []string{cn}
|
||||
ipBased := false
|
||||
var ips []net.IP
|
||||
|
||||
if cn == "" {
|
||||
mapKey = s.ipMapKey()
|
||||
ipBased = true
|
||||
}
|
||||
|
||||
serverNameCert, ok := s.certs[mapKey]
|
||||
if ok {
|
||||
return serverNameCert, nil
|
||||
}
|
||||
|
||||
if ipBased {
|
||||
cn = "cattle"
|
||||
for _, ipStr := range s.ips.Keys() {
|
||||
ip := net.ParseIP(ipStr.(string))
|
||||
if len(ip) > 0 {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed = true
|
||||
|
||||
if s.activeCA == nil {
|
||||
ca, key, err := genCA()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.activeCA = ca
|
||||
s.activeCAKey = key
|
||||
}
|
||||
|
||||
cfg := cert.Config{
|
||||
CommonName: cn,
|
||||
Organization: s.activeCA.Subject.Organization,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
AltNames: cert.AltNames{
|
||||
DNSNames: dnsNames,
|
||||
IPs: ips,
|
||||
},
|
||||
}
|
||||
|
||||
key, err := cert.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := cert.NewSignedCert(cfg, key, s.activeCA, s.activeCAKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsCert := &tls.Certificate{
|
||||
Certificate: [][]byte{
|
||||
cert.Raw,
|
||||
},
|
||||
PrivateKey: key,
|
||||
}
|
||||
|
||||
s.certs[mapKey] = tlsCert
|
||||
return tlsCert, nil
|
||||
}
|
||||
|
||||
func (s *server) cacheIPHandler(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
h, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
h = req.Host
|
||||
}
|
||||
|
||||
ip := net.ParseIP(h)
|
||||
if len(ip) > 0 {
|
||||
if ok, _ := s.ips.ContainsOrAdd(h, ip); ok {
|
||||
go s.save()
|
||||
}
|
||||
}
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) serveHTTPS() error {
|
||||
conf := &tls.Config{
|
||||
GetCertificate: s.getCertificate,
|
||||
PreferServerCipherSuites: true,
|
||||
}
|
||||
|
||||
listener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPSPort, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger := logrus.StandardLogger()
|
||||
server := &http.Server{
|
||||
Handler: s.cacheIPHandler(s.Handler()),
|
||||
ErrorLog: log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags),
|
||||
}
|
||||
|
||||
s.servers = append(s.servers, server)
|
||||
s.startServer(listener, server)
|
||||
|
||||
if s.userConfig.HTTPPort > 0 {
|
||||
httpListener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPPort, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Handler: s.cacheIPHandler(httpRedirect(s.Handler())),
|
||||
ErrorLog: log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags),
|
||||
}
|
||||
|
||||
s.servers = append(s.servers, httpServer)
|
||||
s.startServer(httpListener, httpServer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Approach taken from letsencrypt, except manglePort is specific to us
|
||||
func httpRedirect(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("x-Forwarded-Proto") == "https" ||
|
||||
strings.HasPrefix(r.URL.Path, "/ping") ||
|
||||
strings.HasPrefix(r.URL.Path, "/health") {
|
||||
next.ServeHTTP(rw, r)
|
||||
return
|
||||
}
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(rw, "Use HTTPS", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
target := "https://" + manglePort(r.Host) + r.URL.RequestURI()
|
||||
http.Redirect(rw, r, target, http.StatusFound)
|
||||
})
|
||||
}
|
||||
|
||||
func manglePort(hostport string) string {
|
||||
host, port, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return hostport
|
||||
}
|
||||
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return hostport
|
||||
}
|
||||
|
||||
portInt = ((portInt / 1000) * 1000) + 443
|
||||
|
||||
return net.JoinHostPort(host, strconv.Itoa(portInt))
|
||||
}
|
||||
|
||||
func (s *server) startServer(listener net.Listener, server *http.Server) {
|
||||
go func() {
|
||||
if err := server.Serve(listener); err != nil {
|
||||
logrus.Errorf("server on %v returned err: %v", listener.Addr(), err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *server) Handler() http.Handler {
|
||||
return s.userConfig.Handler
|
||||
}
|
||||
|
||||
func (s *server) newListener(ip string, port int, config *tls.Config) (net.Listener, error) {
|
||||
addr := fmt.Sprintf("%s:%d", ip, port)
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l = tcpKeepAliveListener{l.(*net.TCPListener)}
|
||||
|
||||
if config != nil {
|
||||
l = tls.NewListener(l, config)
|
||||
}
|
||||
|
||||
s.listeners = append(s.listeners, l)
|
||||
logrus.Info("Listening on ", addr)
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (s *server) serveACME() error {
|
||||
manager := autocert.Manager{
|
||||
Cache: autocert.DirCache("certs-cache"),
|
||||
Prompt: s.prompt,
|
||||
HostPolicy: s.hostPolicy,
|
||||
}
|
||||
conf := &tls.Config{
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if hello.ServerName == "localhost" || hello.ServerName == "" {
|
||||
newHello := *hello
|
||||
newHello.ServerName = s.userConfig.Domains[0]
|
||||
return manager.GetCertificate(&newHello)
|
||||
}
|
||||
return manager.GetCertificate(hello)
|
||||
},
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
|
||||
if s.userConfig.HTTPPort > 0 {
|
||||
httpListener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPPort, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Handler: manager.HTTPHandler(nil),
|
||||
ErrorLog: log.New(logrus.StandardLogger().Writer(), "", log.LstdFlags),
|
||||
}
|
||||
s.servers = append(s.servers, httpServer)
|
||||
go func() {
|
||||
if err := httpServer.Serve(httpListener); err != nil {
|
||||
logrus.Errorf("http server returned err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
httpsListener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPSPort, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpsServer := &http.Server{
|
||||
Handler: s.Handler(),
|
||||
ErrorLog: log.New(logrus.StandardLogger().Writer(), "", log.LstdFlags),
|
||||
}
|
||||
s.servers = append(s.servers, httpsServer)
|
||||
go func() {
|
||||
if err := httpsServer.Serve(httpsListener); err != nil {
|
||||
logrus.Errorf("https server returned err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringToCert(certString string) *tls.Certificate {
|
||||
parts := strings.Split(certString, "#")
|
||||
if len(parts) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert, key := parts[0], parts[1]
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rsaKey, err := x509.ParsePKCS1PrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
certBytes, err := base64.StdEncoding.DecodeString(cert)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: rsaKey,
|
||||
}
|
||||
}
|
||||
|
||||
func certToString(cert *tls.Certificate) string {
|
||||
certString := base64.StdEncoding.EncodeToString(cert.Certificate[0])
|
||||
keyString := base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(cert.PrivateKey.(*rsa.PrivateKey)))
|
||||
return certString + "#" + keyString
|
||||
}
|
||||
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
62
types.go
Normal file
62
types.go
Normal file
@ -0,0 +1,62 @@
|
||||
package dynamiclistener
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ListenerConfigStorage interface {
|
||||
Set(*ListenerStatus) (*ListenerStatus, error)
|
||||
Get() (*ListenerStatus, error)
|
||||
}
|
||||
|
||||
type ServerInterface interface {
|
||||
Update(status *ListenerStatus) error
|
||||
CACert() (string, error)
|
||||
}
|
||||
|
||||
type UserConfig struct {
|
||||
// Required fields
|
||||
|
||||
Handler http.Handler
|
||||
HTTPPort int
|
||||
HTTPSPort int
|
||||
CertPath string
|
||||
|
||||
// Optional fields
|
||||
|
||||
KnownIPs []string
|
||||
Domains []string
|
||||
Mode string
|
||||
NoCACerts bool
|
||||
CACerts string
|
||||
Cert string
|
||||
Key string
|
||||
BindAddress string
|
||||
}
|
||||
|
||||
type ListenerStatus struct {
|
||||
Revision string `json:"revision,omitempty"`
|
||||
CACert string `json:"caCert,omitempty"`
|
||||
CAKey string `json:"caKey,omitempty"`
|
||||
GeneratedCerts map[string]string `json:"generatedCerts" norman:"nocreate,noupdate"`
|
||||
KnownIPs map[string]bool `json:"knownIps" norman:"nocreate,noupdate"`
|
||||
}
|
||||
|
||||
func (l *ListenerStatus) DeepCopyInto(t *ListenerStatus) {
|
||||
t.Revision = l.Revision
|
||||
t.CACert = l.CACert
|
||||
t.CAKey = l.CAKey
|
||||
t.GeneratedCerts = copyMap(t.GeneratedCerts)
|
||||
t.KnownIPs = map[string]bool{}
|
||||
for k, v := range l.KnownIPs {
|
||||
t.KnownIPs[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func copyMap(m map[string]string) map[string]string {
|
||||
ret := map[string]string{}
|
||||
for k, v := range m {
|
||||
ret[k] = v
|
||||
}
|
||||
return ret
|
||||
}
|
Loading…
Reference in New Issue
Block a user