mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
commit
6c3688a435
52 changed files with 3946 additions and 154 deletions
1
Makefile
1
Makefile
|
@ -104,6 +104,7 @@ update-vendor:
|
|||
k8s.io/client-go/... \
|
||||
k8s.io/gengo/args \
|
||||
k8s.io/apiextensions-apiserver \
|
||||
github.com/arangodb-helper/go-certificates \
|
||||
github.com/arangodb/go-driver \
|
||||
github.com/cenkalti/backoff \
|
||||
github.com/dchest/uniuri \
|
||||
|
|
202
deps/github.com/arangodb-helper/go-certificates/LICENSE
vendored
Normal file
202
deps/github.com/arangodb-helper/go-certificates/LICENSE
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
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
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 ArangoDB GmbH
|
||||
|
||||
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.
|
4
deps/github.com/arangodb-helper/go-certificates/README.md
vendored
Normal file
4
deps/github.com/arangodb-helper/go-certificates/README.md
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# go-certificates
|
||||
|
||||
Library for golang code related to creating certificates.
|
||||
|
101
deps/github.com/arangodb-helper/go-certificates/ca.go
vendored
Normal file
101
deps/github.com/arangodb-helper/go-certificates/ca.go
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CA struct {
|
||||
Certificate []*x509.Certificate
|
||||
PrivateKey interface{}
|
||||
}
|
||||
|
||||
// LoadCAFromPEM parses the given certificate & key into a CA instance.
|
||||
func LoadCAFromPEM(cert, key string) (CA, error) {
|
||||
certs, privKey, err := LoadFromPEM(cert, key)
|
||||
if err != nil {
|
||||
return CA{}, maskAny(err)
|
||||
}
|
||||
return CA{
|
||||
Certificate: certs,
|
||||
PrivateKey: privKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoadFromPEM parses the given certificate & key into a certificate slice & private key.
|
||||
func LoadFromPEM(cert, key string) ([]*x509.Certificate, interface{}, error) {
|
||||
var certs []*x509.Certificate
|
||||
|
||||
// Parse certificate
|
||||
pemCerts := []byte(cert)
|
||||
for len(pemCerts) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemCerts = pem.Decode(pemCerts)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, maskAny(err)
|
||||
}
|
||||
|
||||
certs = append(certs, c)
|
||||
}
|
||||
if len(certs) == 0 {
|
||||
return nil, nil, maskAny(fmt.Errorf("No CERTIFICATE's found in '%s'", cert))
|
||||
}
|
||||
|
||||
// Parse key
|
||||
pemKey := []byte(key)
|
||||
var privKey interface{}
|
||||
for len(pemKey) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemKey = pem.Decode(pemKey)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if block.Type == "PRIVATE KEY" || strings.HasSuffix(block.Type, " PRIVATE KEY") {
|
||||
if privKey == nil {
|
||||
var err error
|
||||
privKey, err = parsePrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, maskAny(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if privKey == nil {
|
||||
return nil, nil, maskAny(fmt.Errorf("No PRIVATE KEY found in '%s'", key))
|
||||
}
|
||||
|
||||
return certs, privKey, nil
|
||||
}
|
403
deps/github.com/arangodb-helper/go-certificates/cli/certificates.go
vendored
Normal file
403
deps/github.com/arangodb-helper/go-certificates/cli/certificates.go
vendored
Normal file
|
@ -0,0 +1,403 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
certificates "github.com/arangodb-helper/go-certificates"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
// TLS valid for defaults
|
||||
defaultTLSValidFor = time.Hour * 24 * 365 * 2 // 2 years
|
||||
defaultTLSCAValidFor = time.Hour * 24 * 365 * 15 // 15 years
|
||||
// Client authentication valid for defaults
|
||||
defaultClientAuthValidFor = time.Hour * 24 * 365 * 1 // 1 years
|
||||
defaultClientAuthCAValidFor = time.Hour * 24 * 365 * 15 // 15 years
|
||||
)
|
||||
|
||||
var (
|
||||
logFatal func(error, string)
|
||||
showUsage func(cmd *cobra.Command, args []string)
|
||||
cmdCreate = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create certificates",
|
||||
Run: cmdShowUsage,
|
||||
}
|
||||
cmdCreateJWTSecret = &cobra.Command{
|
||||
Use: "jwt-secret",
|
||||
Short: "Create a JWT secret and save it in a plain text file",
|
||||
Run: cmdCreateJWTSecretRun,
|
||||
}
|
||||
cmdCreateTLS = &cobra.Command{
|
||||
Use: "tls",
|
||||
Short: "Create TLS certificates",
|
||||
Run: cmdShowUsage,
|
||||
}
|
||||
cmdCreateTLSCA = &cobra.Command{
|
||||
Use: "ca",
|
||||
Short: "Create a CA certificate used to sign TLS certificates",
|
||||
Run: cmdCreateTLSCARun,
|
||||
}
|
||||
cmdCreateTLSKeyFile = &cobra.Command{
|
||||
Use: "keyfile",
|
||||
Short: "Create a TLS certificate and save it as keyfile",
|
||||
Run: cmdCreateTLSKeyFileRun,
|
||||
}
|
||||
cmdCreateTLSCertificate = &cobra.Command{
|
||||
Use: "certificate",
|
||||
Short: "Create a TLS certificate and save it as crt, key files",
|
||||
Run: cmdCreateTLSCertificateRun,
|
||||
}
|
||||
cmdCreateTLSKeystore = &cobra.Command{
|
||||
Use: "keystore",
|
||||
Short: "Create a TLS certificate and save it as java keystore files",
|
||||
Run: cmdCreateTLSKeystoreRun,
|
||||
}
|
||||
cmdCreateClientAuth = &cobra.Command{
|
||||
Use: "client-auth",
|
||||
Short: "Create client authentication certificates",
|
||||
Run: cmdShowUsage,
|
||||
}
|
||||
cmdCreateClientAuthCA = &cobra.Command{
|
||||
Use: "ca",
|
||||
Short: "Create a CA certificate used to sign client authentication certificates",
|
||||
Run: cmdCreateClientAuthCARun,
|
||||
}
|
||||
cmdCreateClientAuthKeyFile = &cobra.Command{
|
||||
Use: "keyfile",
|
||||
Short: "Create a client authentication certificate and save it as keyfile",
|
||||
Run: cmdCreateClientAuthKeyFileRun,
|
||||
}
|
||||
|
||||
createOptions struct {
|
||||
jwtsecret createJWTSecretOptions
|
||||
tls struct {
|
||||
ca createCAOptions
|
||||
keyFile createKeyFileOptions
|
||||
certificate createCertificateOptions
|
||||
keystore createKeystoreOptions
|
||||
}
|
||||
clientAuth struct {
|
||||
ca createCAOptions
|
||||
keyFile createKeyFileOptions
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
type createJWTSecretOptions struct {
|
||||
secretFile string
|
||||
secretLength int
|
||||
}
|
||||
|
||||
func (o *createJWTSecretOptions) ConfigureFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&o.secretFile, "secret", "secret.jwt", "Filename of the generated JWT secret")
|
||||
f.IntVar(&o.secretLength, "length", 32, "Number of bytes in the secret")
|
||||
}
|
||||
|
||||
func (o *createJWTSecretOptions) CreateSecret() {
|
||||
if o.secretFile == "" {
|
||||
logFatal(nil, "--secret missing")
|
||||
}
|
||||
|
||||
// Create secrey
|
||||
secret := make([]byte, o.secretLength)
|
||||
rand.Read(secret)
|
||||
|
||||
// Encode as hex & store
|
||||
encoded := hex.EncodeToString(secret)
|
||||
mustWriteFile(encoded, o.secretFile, 0600, "secret")
|
||||
|
||||
fmt.Printf("Created JWT secret in %s\n", o.secretFile)
|
||||
fmt.Println("Make sure to store this files in a secure location.")
|
||||
}
|
||||
|
||||
type createCAOptions struct {
|
||||
certFile string
|
||||
keyFile string
|
||||
validFor time.Duration
|
||||
ecdsaCurve string
|
||||
}
|
||||
|
||||
func (o *createCAOptions) ConfigureFlags(f *pflag.FlagSet, defaultFName string, defaultValidFor time.Duration) {
|
||||
f.StringVar(&o.certFile, "cert", defaultFName+".crt", "Filename of the generated CA certificate")
|
||||
f.StringVar(&o.keyFile, "key", defaultFName+".key", "Filename of the generated CA private key")
|
||||
f.DurationVar(&o.validFor, "validfor", defaultValidFor, "Lifetime of the certificate until expiration")
|
||||
f.StringVar(&o.ecdsaCurve, "curve", "P521", "ECDSA curve used for private key")
|
||||
}
|
||||
|
||||
func (o *createCAOptions) CreateCA() {
|
||||
// Create certificate
|
||||
options := certificates.CreateCertificateOptions{
|
||||
ValidFor: o.validFor,
|
||||
ECDSACurve: o.ecdsaCurve,
|
||||
IsCA: true,
|
||||
}
|
||||
cert, key, err := certificates.CreateCertificate(options, nil)
|
||||
if err != nil {
|
||||
logFatal(err, "Failed to create certificate")
|
||||
}
|
||||
|
||||
// Save certificate
|
||||
mustWriteFile(cert, o.certFile, 0644, "cert")
|
||||
mustWriteFile(key, o.keyFile, 0600, "key")
|
||||
|
||||
fmt.Printf("Created CA certificate & key in %s, %s\n", o.certFile, o.keyFile)
|
||||
fmt.Println("Make sure to store these files in a secure location.")
|
||||
}
|
||||
|
||||
type createCertificateBaseOptions struct {
|
||||
caCertFile string
|
||||
caKeyFile string
|
||||
hosts []string
|
||||
emailAddresses []string
|
||||
validFor time.Duration
|
||||
ecdsaCurve string
|
||||
}
|
||||
|
||||
func (o *createCertificateBaseOptions) ConfigureFlags(f *pflag.FlagSet, defaultCAFName, defaultFName string, defaultValidFor time.Duration) {
|
||||
f.StringVar(&o.caCertFile, "cacert", defaultCAFName+".crt", "File containing TLS CA certificate")
|
||||
f.StringVar(&o.caKeyFile, "cakey", defaultCAFName+".key", "File containing TLS CA private key")
|
||||
f.StringSliceVar(&o.hosts, "host", nil, "Host name to include in the certificate")
|
||||
f.StringSliceVar(&o.emailAddresses, "email", nil, "Email address to include in the certificate")
|
||||
f.DurationVar(&o.validFor, "validfor", defaultValidFor, "Lifetime of the certificate until expiration")
|
||||
f.StringVar(&o.ecdsaCurve, "curve", "P521", "ECDSA curve used for private key")
|
||||
}
|
||||
|
||||
// Create a certificate from given options.
|
||||
// Returns: certificate content, key content, ca-certificate content
|
||||
func (o *createCertificateBaseOptions) CreateCertificate(isClientAuth bool) (string, string, string) {
|
||||
// Load data
|
||||
caCert := mustReadFile(o.caCertFile, "cacert")
|
||||
caKey := mustReadFile(o.caKeyFile, "cakey")
|
||||
ca, err := certificates.LoadCAFromPEM(caCert, caKey)
|
||||
if err != nil {
|
||||
logFatal(err, "Failed to load CA")
|
||||
}
|
||||
|
||||
// Create certificate
|
||||
options := certificates.CreateCertificateOptions{
|
||||
Hosts: o.hosts,
|
||||
EmailAddresses: o.emailAddresses,
|
||||
ValidFor: o.validFor,
|
||||
ECDSACurve: o.ecdsaCurve,
|
||||
IsClientAuth: isClientAuth,
|
||||
}
|
||||
cert, key, err := certificates.CreateCertificate(options, &ca)
|
||||
if err != nil {
|
||||
logFatal(err, "Failed to create certificate")
|
||||
}
|
||||
|
||||
return cert, key, caCert
|
||||
}
|
||||
|
||||
type createKeyFileOptions struct {
|
||||
createCertificateBaseOptions
|
||||
keyFile string
|
||||
}
|
||||
|
||||
func (o *createKeyFileOptions) ConfigureFlags(f *pflag.FlagSet, defaultCAFName, defaultFName string, defaultValidFor time.Duration) {
|
||||
o.createCertificateBaseOptions.ConfigureFlags(f, defaultCAFName, defaultFName, defaultValidFor)
|
||||
f.StringVar(&o.keyFile, "keyfile", defaultFName+".keyfile", "Filename of keyfile to generate")
|
||||
}
|
||||
|
||||
func (o *createKeyFileOptions) CreateKeyFile(isClientAuth bool) {
|
||||
// Create certificate + key
|
||||
cert, key, _ := o.createCertificateBaseOptions.CreateCertificate(isClientAuth)
|
||||
|
||||
// Save certificate
|
||||
mustWriteKeyFile(cert, key, o.keyFile, "keyfile")
|
||||
|
||||
fmt.Printf("Created keyfile in %s\n", o.keyFile)
|
||||
fmt.Println("Make sure to store this file in a secure location.")
|
||||
}
|
||||
|
||||
type createCertificateOptions struct {
|
||||
createCertificateBaseOptions
|
||||
certFile string
|
||||
keyFile string
|
||||
}
|
||||
|
||||
func (o *createCertificateOptions) ConfigureFlags(f *pflag.FlagSet, defaultCAFName, defaultFName string, defaultValidFor time.Duration) {
|
||||
o.createCertificateBaseOptions.ConfigureFlags(f, defaultCAFName, defaultFName, defaultValidFor)
|
||||
f.StringVar(&o.certFile, "cert", defaultFName+".crt", "Filename of the generated certificate")
|
||||
f.StringVar(&o.keyFile, "key", defaultFName+".key", "Filename of the generated private key")
|
||||
}
|
||||
|
||||
func (o *createCertificateOptions) CreateCertificate(isClientAuth bool) {
|
||||
// Create certificate + key
|
||||
cert, key, _ := o.createCertificateBaseOptions.CreateCertificate(isClientAuth)
|
||||
|
||||
// Save certificate
|
||||
mustWriteFile(cert, o.certFile, 0644, "cert")
|
||||
mustWriteFile(key, o.keyFile, 0600, "key")
|
||||
|
||||
fmt.Printf("Created certificate & key in %s, %s\n", o.certFile, o.keyFile)
|
||||
fmt.Println("Make sure to store these files in a secure location.")
|
||||
}
|
||||
|
||||
type createKeystoreOptions struct {
|
||||
createCertificateBaseOptions
|
||||
keystoreFile string
|
||||
keystorePassword string
|
||||
alias string
|
||||
}
|
||||
|
||||
func (o *createKeystoreOptions) ConfigureFlags(f *pflag.FlagSet, defaultCAFName, defaultFName string, defaultValidFor time.Duration) {
|
||||
o.createCertificateBaseOptions.ConfigureFlags(f, defaultCAFName, defaultFName, defaultValidFor)
|
||||
f.StringVar(&o.keystoreFile, "keystore", defaultFName+".jks", "Filename of the generated keystore")
|
||||
f.StringVar(&o.keystorePassword, "keystore-password", "", "Password of the generated keystore")
|
||||
f.StringVar(&o.alias, "alias", "", "Aliases use to store the certificate under in the keystore")
|
||||
}
|
||||
|
||||
func (o *createKeystoreOptions) CreateCertificate(isClientAuth bool) {
|
||||
if o.alias == "" {
|
||||
logFatal(nil, "--alias missing")
|
||||
}
|
||||
if o.keystorePassword == "" {
|
||||
logFatal(nil, "--keystore-password missing")
|
||||
}
|
||||
// Create certificate + key
|
||||
cert, key, caCert := o.createCertificateBaseOptions.CreateCertificate(isClientAuth)
|
||||
|
||||
// Encode as keystore
|
||||
ks, err := certificates.CreateKeystore(cert, key, caCert, o.alias, []byte(o.keystorePassword))
|
||||
if err != nil {
|
||||
logFatal(err, "Failed to create keystore")
|
||||
}
|
||||
mustWriteFile(string(ks), o.keystoreFile, 0600, "keystore")
|
||||
|
||||
fmt.Printf("Created keystore in %s\n", o.keystoreFile)
|
||||
fmt.Println("Make sure to store this files in a secure location.")
|
||||
}
|
||||
|
||||
// AddCommands adds all creations commands to the given root command.
|
||||
func AddCommands(cmd *cobra.Command, logFatalFunc func(error, string), showUsageFunc func(cmd *cobra.Command, args []string)) {
|
||||
logFatal = logFatalFunc
|
||||
showUsage = showUsageFunc
|
||||
|
||||
cmd.AddCommand(cmdCreate)
|
||||
cmdCreate.AddCommand(cmdCreateJWTSecret)
|
||||
cmdCreate.AddCommand(cmdCreateTLS)
|
||||
cmdCreateTLS.AddCommand(cmdCreateTLSCA)
|
||||
cmdCreateTLS.AddCommand(cmdCreateTLSKeyFile)
|
||||
cmdCreateTLS.AddCommand(cmdCreateTLSCertificate)
|
||||
cmdCreateTLS.AddCommand(cmdCreateTLSKeystore)
|
||||
cmdCreate.AddCommand(cmdCreateClientAuth)
|
||||
cmdCreateClientAuth.AddCommand(cmdCreateClientAuthCA)
|
||||
cmdCreateClientAuth.AddCommand(cmdCreateClientAuthKeyFile)
|
||||
|
||||
createOptions.jwtsecret.ConfigureFlags(cmdCreateJWTSecret.Flags())
|
||||
createOptions.tls.ca.ConfigureFlags(cmdCreateTLSCA.Flags(), "tls-ca", defaultTLSCAValidFor)
|
||||
createOptions.tls.keyFile.ConfigureFlags(cmdCreateTLSKeyFile.Flags(), "tls-ca", "tls", defaultTLSValidFor)
|
||||
createOptions.tls.certificate.ConfigureFlags(cmdCreateTLSCertificate.Flags(), "tls-ca", "tls", defaultTLSValidFor)
|
||||
createOptions.tls.keystore.ConfigureFlags(cmdCreateTLSKeystore.Flags(), "tls-ca", "tls", defaultTLSValidFor)
|
||||
createOptions.clientAuth.ca.ConfigureFlags(cmdCreateClientAuthCA.Flags(), "client-auth-ca", defaultClientAuthCAValidFor)
|
||||
createOptions.clientAuth.keyFile.ConfigureFlags(cmdCreateClientAuthKeyFile.Flags(), "client-auth-ca", "client-auth", defaultClientAuthValidFor)
|
||||
}
|
||||
|
||||
// Cobra run function using the usage of the given command
|
||||
func cmdShowUsage(cmd *cobra.Command, args []string) {
|
||||
showUsage(cmd, args)
|
||||
}
|
||||
|
||||
// cmdCreateJWTSecretRun creates a JWT secret and writes it to file
|
||||
func cmdCreateJWTSecretRun(cmd *cobra.Command, args []string) {
|
||||
createOptions.jwtsecret.CreateSecret()
|
||||
}
|
||||
|
||||
// cmdCreateTLSCARun creates a CA used to sign TLS certificates
|
||||
func cmdCreateTLSCARun(cmd *cobra.Command, args []string) {
|
||||
createOptions.tls.ca.CreateCA()
|
||||
}
|
||||
|
||||
// cmdCreateTLSKeyFileRun creates a TLS certificate and save it as keyfile
|
||||
func cmdCreateTLSKeyFileRun(cmd *cobra.Command, args []string) {
|
||||
isClientAuth := false
|
||||
createOptions.tls.keyFile.CreateKeyFile(isClientAuth)
|
||||
}
|
||||
|
||||
// cmdCreateTLSCertificateRun creates a TLS certificate and save it as crt+key file
|
||||
func cmdCreateTLSCertificateRun(cmd *cobra.Command, args []string) {
|
||||
isClientAuth := false
|
||||
createOptions.tls.certificate.CreateCertificate(isClientAuth)
|
||||
}
|
||||
|
||||
// cmdCreateTLSKeystoreRun creates a TLS certificate and save it as java keystore file.
|
||||
func cmdCreateTLSKeystoreRun(cmd *cobra.Command, args []string) {
|
||||
isClientAuth := false
|
||||
createOptions.tls.keystore.CreateCertificate(isClientAuth)
|
||||
}
|
||||
|
||||
// cmdCreateClientAuthCARun creates a CA used to sign client authentication certificates
|
||||
func cmdCreateClientAuthCARun(cmd *cobra.Command, args []string) {
|
||||
createOptions.clientAuth.ca.CreateCA()
|
||||
}
|
||||
|
||||
// cmdCreateClientAuthKeyFileRun creates a client authentication certificate and save it as keyfile
|
||||
func cmdCreateClientAuthKeyFileRun(cmd *cobra.Command, args []string) {
|
||||
isClientAuth := true
|
||||
createOptions.clientAuth.keyFile.CreateKeyFile(isClientAuth)
|
||||
}
|
||||
|
||||
func mustWriteKeyFile(cert, key string, filename string, flagName string) {
|
||||
if filename == "" {
|
||||
logFatal(nil, fmt.Sprintf("Missing filename for option --%s", flagName))
|
||||
}
|
||||
if err := certificates.SaveKeyFile(cert, key, filename); err != nil {
|
||||
logFatal(err, fmt.Sprintf("Failed to write %s", filename))
|
||||
}
|
||||
}
|
||||
|
||||
func mustWriteFile(content string, filename string, mode os.FileMode, flagName string) {
|
||||
if filename == "" {
|
||||
logFatal(nil, fmt.Sprintf("Missing filename for option --%s", flagName))
|
||||
}
|
||||
folder := filepath.Dir(filename)
|
||||
if folder != "" {
|
||||
os.MkdirAll(folder, 0755)
|
||||
}
|
||||
if err := ioutil.WriteFile(filename, []byte(content), mode); err != nil {
|
||||
logFatal(err, fmt.Sprintf("Failed to write %s", filename))
|
||||
}
|
||||
}
|
||||
|
||||
func mustReadFile(filename string, flagName string) string {
|
||||
if filename == "" {
|
||||
logFatal(nil, fmt.Sprintf("Missing filename for option --%s", flagName))
|
||||
}
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
logFatal(err, fmt.Sprintf("Failed to read %s", filename))
|
||||
}
|
||||
return string(content)
|
||||
}
|
195
deps/github.com/arangodb-helper/go-certificates/create.go
vendored
Normal file
195
deps/github.com/arangodb-helper/go-certificates/create.go
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultValidFor = time.Hour * 24 * 365
|
||||
defaultRSABits = 2048
|
||||
)
|
||||
|
||||
type CreateCertificateOptions struct {
|
||||
CommonName string // Common name set in the certificate. If not specified, defaults to first email address, then first host and if all not set 'ArangoDB'.
|
||||
Hosts []string // Comma-separated hostnames and IPs to generate a certificate for
|
||||
EmailAddresses []string // List of email address to include in the certificate as alternative name
|
||||
ValidFrom time.Time // Creation data of the certificate
|
||||
ValidFor time.Duration // Duration that certificate is valid for
|
||||
IsCA bool // Whether this cert should be its own Certificate Authority
|
||||
IsClientAuth bool // Whether this cert can be used for client authentication
|
||||
RSABits int // Size of RSA key to generate. Ignored if ECDSACurve is set
|
||||
ECDSACurve string // ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521
|
||||
}
|
||||
|
||||
// CreateCertificate creates a certificate according to the given configuration.
|
||||
// If ca is nil, the certificate will be self-signed, otherwise the certificate
|
||||
// will be signed by the given CA certificate+key.
|
||||
// The resulting certificate + private key will be PEM encoded and returned as string (cert, priv, error).
|
||||
func CreateCertificate(options CreateCertificateOptions, ca *CA) (string, string, error) {
|
||||
// Create private key
|
||||
var priv interface{}
|
||||
var err error
|
||||
switch options.ECDSACurve {
|
||||
case "":
|
||||
if options.RSABits == 0 {
|
||||
options.RSABits = defaultRSABits
|
||||
}
|
||||
priv, err = rsa.GenerateKey(rand.Reader, options.RSABits)
|
||||
case "P224":
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
||||
case "P256":
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
case "P384":
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
case "P521":
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
default:
|
||||
return "", "", maskAny(fmt.Errorf("Unknown curve '%s'", options.ECDSACurve))
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
if options.ValidFor == 0 {
|
||||
options.ValidFor = defaultValidFor
|
||||
}
|
||||
notAfter := notBefore.Add(options.ValidFor)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return "", "", maskAny(fmt.Errorf("failed to generate serial number: %v", err))
|
||||
}
|
||||
|
||||
commonName := "ArangoDB"
|
||||
if options.CommonName != "" {
|
||||
commonName = options.CommonName
|
||||
} else if len(options.EmailAddresses) > 0 {
|
||||
commonName = options.EmailAddresses[0]
|
||||
} else if len(options.Hosts) > 0 {
|
||||
commonName = options.Hosts[0]
|
||||
}
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: commonName,
|
||||
Organization: []string{"ArangoDB"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
for _, h := range options.Hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, h)
|
||||
}
|
||||
}
|
||||
template.EmailAddresses = append(template.EmailAddresses, options.EmailAddresses...)
|
||||
|
||||
if options.IsCA {
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
}
|
||||
if options.IsClientAuth {
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
|
||||
} else {
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
|
||||
}
|
||||
|
||||
// Create the certificate
|
||||
var derBytes []byte
|
||||
if ca != nil {
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, &template, ca.Certificate[0], publicKey(priv), ca.PrivateKey)
|
||||
if err != nil {
|
||||
return "", "", maskAny(fmt.Errorf("Failed to create signed certificate: %v", err))
|
||||
}
|
||||
} else {
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
||||
if err != nil {
|
||||
return "", "", maskAny(fmt.Errorf("Failed to create self-signed certificate: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Encode certificate
|
||||
// Public key
|
||||
buf := &bytes.Buffer{}
|
||||
pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
if ca != nil {
|
||||
for _, c := range ca.Certificate {
|
||||
pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})
|
||||
}
|
||||
}
|
||||
certPem := buf.String()
|
||||
|
||||
// Private key
|
||||
buf = &bytes.Buffer{}
|
||||
pem.Encode(buf, pemBlockForKey(priv))
|
||||
privPem := buf.String()
|
||||
|
||||
return certPem, privPem, nil
|
||||
}
|
||||
|
||||
func publicKey(priv interface{}) interface{} {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
case *ecdsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func pemBlockForKey(priv interface{}) *pem.Block {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
||||
case *ecdsa.PrivateKey:
|
||||
b, err := x509.MarshalECPrivateKey(k)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
29
deps/github.com/arangodb-helper/go-certificates/error.go
vendored
Normal file
29
deps/github.com/arangodb-helper/go-certificates/error.go
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
var (
|
||||
maskAny = errors.WithStack
|
||||
)
|
50
deps/github.com/arangodb-helper/go-certificates/expiration_date.go
vendored
Normal file
50
deps/github.com/arangodb-helper/go-certificates/expiration_date.go
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetCertificateExpirationDate returns the expiration date of the TLS certificate
|
||||
// found in the given config.
|
||||
// Returns: ExpirationDate, FoundExpirationDate
|
||||
func GetCertificateExpirationDate(config *tls.Config) (time.Time, bool) {
|
||||
if config == nil || len(config.Certificates) == 0 {
|
||||
return time.Time{}, false
|
||||
}
|
||||
var expDate time.Time
|
||||
found := false
|
||||
for _, raw := range config.Certificates[0].Certificate {
|
||||
if c, err := x509.ParseCertificate(raw); err == nil {
|
||||
d := c.NotAfter
|
||||
if !found || d.Before(expDate) {
|
||||
expDate = d
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return expDate, found
|
||||
}
|
164
deps/github.com/arangodb-helper/go-certificates/keyfile.go
vendored
Normal file
164
deps/github.com/arangodb-helper/go-certificates/keyfile.go
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LoadKeyFile loads a SSL keyfile formatted for the arangod server.
|
||||
func LoadKeyFile(keyFile string) (tls.Certificate, error) {
|
||||
raw, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, maskAny(err)
|
||||
}
|
||||
|
||||
result := tls.Certificate{}
|
||||
for {
|
||||
var derBlock *pem.Block
|
||||
derBlock, raw = pem.Decode(raw)
|
||||
if derBlock == nil {
|
||||
break
|
||||
}
|
||||
if derBlock.Type == "CERTIFICATE" {
|
||||
result.Certificate = append(result.Certificate, derBlock.Bytes)
|
||||
} else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
|
||||
if result.PrivateKey == nil {
|
||||
result.PrivateKey, err = parsePrivateKey(derBlock.Bytes)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, maskAny(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(result.Certificate) == 0 {
|
||||
return tls.Certificate{}, maskAny(fmt.Errorf("No certificates found in %s", keyFile))
|
||||
}
|
||||
if result.PrivateKey == nil {
|
||||
return tls.Certificate{}, maskAny(fmt.Errorf("No private key found in %s", keyFile))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ExtractCACertificateFromKeyFile loads a SSL keyfile formatted for the arangod server and
|
||||
// extracts the CA certificate(s) from it (if any).
|
||||
func ExtractCACertificateFromKeyFile(keyFile string) (string, error) {
|
||||
raw, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return "", maskAny(err)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
certificatesFound := 0
|
||||
for {
|
||||
var derBlock *pem.Block
|
||||
derBlock, raw = pem.Decode(raw)
|
||||
if derBlock == nil {
|
||||
break
|
||||
}
|
||||
if derBlock.Type == "CERTIFICATE" {
|
||||
certificatesFound++
|
||||
c, err := x509.ParseCertificate(derBlock.Bytes)
|
||||
if err != nil {
|
||||
return "", maskAny(err)
|
||||
}
|
||||
if c.IsCA {
|
||||
pem.Encode(buf, derBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
certPem := buf.String()
|
||||
if certificatesFound == 0 {
|
||||
return "", maskAny(fmt.Errorf("No certificates found in %s", keyFile))
|
||||
}
|
||||
return certPem, nil
|
||||
}
|
||||
|
||||
// SaveKeyFile creates a keyfile with given certificate & key data
|
||||
func SaveKeyFile(cert, key string, filename string) error {
|
||||
folder := filepath.Dir(filename)
|
||||
if folder != "" {
|
||||
os.MkdirAll(folder, 0755)
|
||||
}
|
||||
content := strings.TrimSpace(cert) + "\n" + strings.TrimSpace(key)
|
||||
if err := ioutil.WriteFile(filename, []byte(content), 0600); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
|
||||
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
|
||||
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
|
||||
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey:
|
||||
return key, nil
|
||||
default:
|
||||
return nil, maskAny(errors.New("tls: found unknown private key type in PKCS#8 wrapping"))
|
||||
}
|
||||
}
|
||||
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return nil, maskAny(errors.New("tls: failed to parse private key"))
|
||||
}
|
||||
|
||||
// EncodeToString encodes the given certification information into
|
||||
// 2 strings. The first containing all certificates (PEM encoded),
|
||||
// the second containing the private key (PEM encoded).
|
||||
func EncodeToString(c tls.Certificate) (cert, key string) {
|
||||
// Encode certificates
|
||||
buf := &bytes.Buffer{}
|
||||
for _, x := range c.Certificate {
|
||||
pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: x})
|
||||
}
|
||||
certPem := buf.String()
|
||||
|
||||
// Private key
|
||||
buf = &bytes.Buffer{}
|
||||
pem.Encode(buf, pemBlockForKey(c.PrivateKey))
|
||||
privPem := buf.String()
|
||||
|
||||
return certPem, privPem
|
||||
}
|
181
deps/github.com/arangodb-helper/go-certificates/keystore.go
vendored
Normal file
181
deps/github.com/arangodb-helper/go-certificates/keystore.go
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
keystore "github.com/pavel-v-chernykh/keystore-go"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CreateKeystore creates a java keystore containing the given certificate,
|
||||
// private key & ca certificate(s).
|
||||
func CreateKeystore(cert, key, caCert string, alias string, keystorePassword []byte) ([]byte, error) {
|
||||
ks := make(keystore.KeyStore)
|
||||
|
||||
// Decode CA cert
|
||||
ksCACerts, err := decodeCACertificates(caCert)
|
||||
if err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to decode CA certificates"))
|
||||
}
|
||||
for alias, ksCACert := range ksCACerts {
|
||||
ks[alias] = &keystore.TrustedCertificateEntry{
|
||||
Entry: keystore.Entry{CreationDate: time.Now()},
|
||||
Certificate: ksCACert,
|
||||
}
|
||||
}
|
||||
|
||||
// Decode certificate
|
||||
ksCerts, err := decodeCertificates(cert)
|
||||
if err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to decode certificate"))
|
||||
}
|
||||
|
||||
// Decode private key
|
||||
pk, err := decodePrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to decode private key"))
|
||||
}
|
||||
encPK, err := convertPrivateKeyToPKCS8(pk)
|
||||
if err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to encode private key"))
|
||||
}
|
||||
ks[alias] = &keystore.PrivateKeyEntry{
|
||||
Entry: keystore.Entry{CreationDate: time.Now()},
|
||||
PrivKey: encPK,
|
||||
CertChain: ksCerts,
|
||||
}
|
||||
|
||||
// Encode keystore
|
||||
buf := &bytes.Buffer{}
|
||||
if err := keystore.Encode(buf, ks, keystorePassword); err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to encode keystore"))
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// decodeCACertificates takes a PEM encoded string and decodes all certificates
|
||||
// in it into a map of alias+certificate pairs.
|
||||
func decodeCACertificates(pemContent string) (map[string]keystore.Certificate, error) {
|
||||
ksCerts, err := decodeCertificates(pemContent)
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
|
||||
result := map[string]keystore.Certificate{}
|
||||
for _, ksCert := range ksCerts {
|
||||
caCerts, err := x509.ParseCertificates(ksCert.Content)
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
if len(caCerts) == 0 {
|
||||
return nil, maskAny(errors.New("Failed to parse CA certificate"))
|
||||
}
|
||||
|
||||
for _, caCert := range caCerts {
|
||||
commonName := caCert.Subject.CommonName
|
||||
if commonName == "" {
|
||||
return nil, maskAny(fmt.Errorf("Missing common name in CA certificate '%s'", caCert.Subject))
|
||||
}
|
||||
alias := strings.Replace(strings.ToLower(commonName), " ", "", -1)
|
||||
result[alias] = ksCert
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// decodeCertificates takes a PEM encoded string and decodes it a list of
|
||||
// keystore certificates.
|
||||
func decodeCertificates(pemContent string) ([]keystore.Certificate, error) {
|
||||
if pemContent == "" {
|
||||
return nil, nil
|
||||
}
|
||||
blocks, err := decodePEMString(pemContent)
|
||||
if err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to decode certificates"))
|
||||
}
|
||||
var result []keystore.Certificate
|
||||
for _, b := range blocks {
|
||||
if b.Type == "CERTIFICATE" {
|
||||
result = append(result, keystore.Certificate{
|
||||
Type: "X509",
|
||||
Content: b.Bytes,
|
||||
})
|
||||
} else {
|
||||
return nil, maskAny(fmt.Errorf("Unexpected block of type '%s' in CA certificates", b.Type))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// decodePrivateKey takes a PEM encoded string and decodes its private key entry.
|
||||
func decodePrivateKey(pemContent string) (interface{}, error) {
|
||||
blocks, err := decodePEMString(pemContent)
|
||||
if err != nil {
|
||||
return nil, maskAny(errors.Wrap(err, "Failed to decode private key"))
|
||||
}
|
||||
var result interface{}
|
||||
for _, b := range blocks {
|
||||
if b.Type == "PRIVATE KEY" || strings.HasSuffix(b.Type, " PRIVATE KEY") {
|
||||
if result != nil {
|
||||
return nil, maskAny(errors.New("Found multiple private keys"))
|
||||
}
|
||||
privKey, err := parsePrivateKey(b.Bytes)
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
result = privKey
|
||||
} else {
|
||||
return nil, maskAny(fmt.Errorf("Unexpected block of type '%s' in CA certificates", b.Type))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// decodePEMString takes a PEM encoded string and decodes it into pem blocks.
|
||||
func decodePEMString(pemContent string) ([]*pem.Block, error) {
|
||||
var blocks []*pem.Block
|
||||
content := []byte(pemContent)
|
||||
for {
|
||||
b, remaining := pem.Decode(content)
|
||||
if b == nil {
|
||||
if len(blocks) > 0 {
|
||||
return blocks, nil
|
||||
}
|
||||
return nil, maskAny(errors.New("failed to decode PEM blocks"))
|
||||
}
|
||||
blocks = append(blocks, b)
|
||||
if len(remaining) == 0 {
|
||||
return blocks, nil
|
||||
}
|
||||
content = remaining
|
||||
}
|
||||
}
|
148
deps/github.com/arangodb-helper/go-certificates/pkcs8.go
vendored
Normal file
148
deps/github.com/arangodb-helper/go-certificates/pkcs8.go
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Unecrypted PKCS8
|
||||
var (
|
||||
oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
|
||||
oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
|
||||
oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
|
||||
)
|
||||
|
||||
// Copy from crypto/x509
|
||||
var (
|
||||
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
|
||||
)
|
||||
|
||||
// Copy from crypto/x509
|
||||
var (
|
||||
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
|
||||
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
|
||||
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
|
||||
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
|
||||
)
|
||||
|
||||
// Copy from crypto/x509
|
||||
func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) {
|
||||
switch curve {
|
||||
case elliptic.P224():
|
||||
return oidNamedCurveP224, true
|
||||
case elliptic.P256():
|
||||
return oidNamedCurveP256, true
|
||||
case elliptic.P384():
|
||||
return oidNamedCurveP384, true
|
||||
case elliptic.P521():
|
||||
return oidNamedCurveP521, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type privateKeyInfo struct {
|
||||
Version int
|
||||
PrivateKeyAlgorithm []asn1.ObjectIdentifier
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
// Encrypted PKCS8
|
||||
/*type pbkdf2Params struct {
|
||||
Salt []byte
|
||||
IterationCount int
|
||||
}
|
||||
|
||||
type pbkdf2Algorithms struct {
|
||||
IDPBKDF2 asn1.ObjectIdentifier
|
||||
PBKDF2Params pbkdf2Params
|
||||
}
|
||||
|
||||
type pbkdf2Encs struct {
|
||||
EncryAlgo asn1.ObjectIdentifier
|
||||
IV []byte
|
||||
}
|
||||
|
||||
type pbes2Params struct {
|
||||
KeyDerivationFunc pbkdf2Algorithms
|
||||
EncryptionScheme pbkdf2Encs
|
||||
}
|
||||
|
||||
type pbes2Algorithms struct {
|
||||
IDPBES2 asn1.ObjectIdentifier
|
||||
PBES2Params pbes2Params
|
||||
}
|
||||
|
||||
type encryptedPrivateKeyInfo struct {
|
||||
EncryptionAlgorithm pbes2Algorithms
|
||||
EncryptedData []byte
|
||||
}*/
|
||||
|
||||
func convertPrivateKeyToPKCS8(priv interface{}) (der []byte, err error) {
|
||||
var rb []byte
|
||||
var pkey privateKeyInfo
|
||||
|
||||
switch priv := priv.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
eckey, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
|
||||
oidNamedCurve, ok := oidFromNamedCurve(priv.Curve)
|
||||
if !ok {
|
||||
return nil, maskAny(errors.New("pkcs8: unknown elliptic curve"))
|
||||
}
|
||||
|
||||
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
|
||||
// But openssl set to v1 even publicKey is present
|
||||
pkey.Version = 0
|
||||
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
|
||||
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
|
||||
pkey.PrivateKeyAlgorithm[1] = oidNamedCurve
|
||||
pkey.PrivateKey = eckey
|
||||
case *rsa.PrivateKey:
|
||||
|
||||
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
|
||||
// But openssl set to v1 even publicKey is present
|
||||
pkey.Version = 0
|
||||
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
|
||||
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyRSA
|
||||
pkey.PrivateKey = x509.MarshalPKCS1PrivateKey(priv)
|
||||
}
|
||||
|
||||
rb, err = asn1.Marshal(pkey)
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
|
||||
return rb, nil
|
||||
}
|
41
deps/github.com/arangodb-helper/go-certificates/pool.go
vendored
Normal file
41
deps/github.com/arangodb-helper/go-certificates/pool.go
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// LoadCertPool creates a certificate pool from the certificate(s) given in the
|
||||
// given PEM encoded string.
|
||||
func LoadCertPool(certificate string) (*x509.CertPool, error) {
|
||||
if certificate == "" {
|
||||
return nil, nil
|
||||
}
|
||||
certpool := x509.NewCertPool()
|
||||
if success := certpool.AppendCertsFromPEM([]byte(certificate)); !success {
|
||||
return nil, maskAny(fmt.Errorf("Invalid certificate"))
|
||||
}
|
||||
return certpool, nil
|
||||
}
|
53
deps/github.com/arangodb-helper/go-certificates/tls_authentication.go
vendored
Normal file
53
deps/github.com/arangodb-helper/go-certificates/tls_authentication.go
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
type TLSAuthentication interface {
|
||||
CACertificate() string
|
||||
ClientCertificate() string
|
||||
ClientKey() string
|
||||
}
|
||||
|
||||
// CreateTLSConfigFromAuthentication creates a tls.Config object from given configuration.
|
||||
func CreateTLSConfigFromAuthentication(a TLSAuthentication, insecureSkipVerify bool) (*tls.Config, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: insecureSkipVerify,
|
||||
}
|
||||
var err error
|
||||
tlsConfig.RootCAs, err = LoadCertPool(a.CACertificate())
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
if a.ClientCertificate() != "" && a.ClientKey() != "" {
|
||||
clientCert, err := tls.X509KeyPair([]byte(a.ClientCertificate()), []byte(a.ClientKey()))
|
||||
if err != nil {
|
||||
return nil, maskAny(err)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{clientCert}
|
||||
}
|
||||
return tlsConfig, nil
|
||||
}
|
18
deps/github.com/pavel-v-chernykh/keystore-go/.gitignore
vendored
Normal file
18
deps/github.com/pavel-v-chernykh/keystore-go/.gitignore
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
*.o
|
||||
*.a
|
||||
*.so
|
||||
_obj
|
||||
_test
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
_testmain.go
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.iml
|
||||
.idea
|
21
deps/github.com/pavel-v-chernykh/keystore-go/LICENSE
vendored
Normal file
21
deps/github.com/pavel-v-chernykh/keystore-go/LICENSE
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Pavel Chernykh
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
64
deps/github.com/pavel-v-chernykh/keystore-go/README.md
vendored
Normal file
64
deps/github.com/pavel-v-chernykh/keystore-go/README.md
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Keystore
|
||||
A go (golang) implementation of Java [KeyStore][1] encoder/decoder
|
||||
|
||||
Take into account that JKS assumes that private keys are PKCS8 encoded.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pavel-v-chernykh/keystore-go"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func readKeyStore(filename string, password []byte) keystore.KeyStore {
|
||||
f, err := os.Open(filename)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
keyStore, err := keystore.Decode(f, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return keyStore
|
||||
}
|
||||
|
||||
func writeKeyStore(keyStore keystore.KeyStore, filename string, password []byte) {
|
||||
o, err := os.Create(filename)
|
||||
defer o.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = keystore.Encode(o, keyStore, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func zeroing(s []byte) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
password := []byte{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}
|
||||
defer zeroing(password)
|
||||
ks1 := readKeyStore("keystore.jks", password)
|
||||
|
||||
writeKeyStore(ks1, "keystore2.jks", password)
|
||||
|
||||
ks2 := readKeyStore("keystore2.jks", password)
|
||||
|
||||
log.Printf("Is equal: %v\n", reflect.DeepEqual(ks1, ks2))
|
||||
}
|
||||
```
|
||||
|
||||
For more examples explore [examples](examples) dir
|
||||
|
||||
[1]: https://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html#KeyManagement
|
43
deps/github.com/pavel-v-chernykh/keystore-go/common.go
vendored
Normal file
43
deps/github.com/pavel-v-chernykh/keystore-go/common.go
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
const magic uint32 = 0xfeedfeed
|
||||
const (
|
||||
version01 uint32 = 1
|
||||
version02 uint32 = 2
|
||||
)
|
||||
const (
|
||||
privateKeyTag uint32 = 1
|
||||
trustedCertificateTag uint32 = 2
|
||||
)
|
||||
const bufSize = 1024
|
||||
|
||||
var order = binary.BigEndian
|
||||
|
||||
var whitenerMessage = []byte("Mighty Aphrodite")
|
||||
|
||||
func passwordBytes(password []byte) []byte {
|
||||
passwdBytes := make([]byte, 0, len(password)*2)
|
||||
for _, b := range password {
|
||||
passwdBytes = append(passwdBytes, 0, b)
|
||||
}
|
||||
return passwdBytes
|
||||
}
|
||||
|
||||
func zeroing(s []byte) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func millisecondsToTime(ms int64) time.Time {
|
||||
return time.Unix(0, ms*int64(time.Millisecond))
|
||||
}
|
||||
|
||||
func timeToMilliseconds(t time.Time) int64 {
|
||||
return t.UnixNano() / int64(time.Millisecond)
|
||||
}
|
54
deps/github.com/pavel-v-chernykh/keystore-go/common_test.go
vendored
Normal file
54
deps/github.com/pavel-v-chernykh/keystore-go/common_test.go
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestZeroing(t *testing.T) {
|
||||
type zeroingItem struct {
|
||||
input []byte
|
||||
}
|
||||
type zeroingTable []zeroingItem
|
||||
|
||||
var table zeroingTable
|
||||
for i := 0; i < 20; i++ {
|
||||
buf := make([]byte, 4096)
|
||||
rand.Read(buf)
|
||||
table = append(table, zeroingItem{input: buf})
|
||||
}
|
||||
|
||||
for _, tt := range table {
|
||||
zeroing(tt.input)
|
||||
for i := range tt.input {
|
||||
if tt.input[i] != 0 {
|
||||
t.Errorf("Invalid zeroing '%v'", tt.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordBytes(t *testing.T) {
|
||||
type passwordBytesItem struct {
|
||||
input []byte
|
||||
output []byte
|
||||
}
|
||||
var table []passwordBytesItem
|
||||
for i := 0; i < 20; i++ {
|
||||
ibuf := make([]byte, 1024)
|
||||
rand.Read(ibuf)
|
||||
obuf := make([]byte, len(ibuf)*2)
|
||||
for j, k := 0, 0; j < len(obuf); j, k = j+2, k+1 {
|
||||
obuf[j] = 0
|
||||
obuf[j+1] = ibuf[k]
|
||||
}
|
||||
table = append(table, passwordBytesItem{input: ibuf, output: obuf})
|
||||
}
|
||||
for _, tt := range table {
|
||||
output := passwordBytes(tt.input)
|
||||
if !reflect.DeepEqual(output, tt.output) {
|
||||
t.Errorf("Invalid output '%v', '%v'", output, tt.output)
|
||||
}
|
||||
}
|
||||
}
|
273
deps/github.com/pavel-v-chernykh/keystore-go/decoder.go
vendored
Normal file
273
deps/github.com/pavel-v-chernykh/keystore-go/decoder.go
vendored
Normal file
|
@ -0,0 +1,273 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
const defaultCertificateType = "X509"
|
||||
|
||||
// ErrIo indicates i/o error
|
||||
var ErrIo = errors.New("keystore: invalid keystore format")
|
||||
|
||||
// ErrIncorrectMagic indicates incorrect file magic
|
||||
var ErrIncorrectMagic = errors.New("keystore: invalid keystore format")
|
||||
|
||||
// ErrIncorrectVersion indicates incorrect keystore version format
|
||||
var ErrIncorrectVersion = errors.New("keystore: invalid keystore format")
|
||||
|
||||
// ErrIncorrectTag indicates incorrect keystore entry tag
|
||||
var ErrIncorrectTag = errors.New("keystore: invalid keystore format")
|
||||
|
||||
// ErrIncorrectPrivateKey indicates incorrect private key entry content
|
||||
var ErrIncorrectPrivateKey = errors.New("keystore: invalid private key format")
|
||||
|
||||
// ErrInvalidDigest indicates that keystore was tampered or password was incorrect
|
||||
var ErrInvalidDigest = errors.New("keystore: invalid digest")
|
||||
|
||||
type keyStoreDecoder struct {
|
||||
r io.Reader
|
||||
b [bufSize]byte
|
||||
md hash.Hash
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readUint16() (uint16, error) {
|
||||
const blockSize = 2
|
||||
_, err := io.ReadFull(ksd.r, ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return 0, ErrIo
|
||||
}
|
||||
_, err = ksd.md.Write(ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return order.Uint16(ksd.b[:blockSize]), nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readUint32() (uint32, error) {
|
||||
const blockSize = 4
|
||||
_, err := io.ReadFull(ksd.r, ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return 0, ErrIo
|
||||
}
|
||||
_, err = ksd.md.Write(ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return order.Uint32(ksd.b[:blockSize]), nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readUint64() (uint64, error) {
|
||||
const blockSize = 8
|
||||
_, err := io.ReadFull(ksd.r, ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return 0, ErrIo
|
||||
}
|
||||
_, err = ksd.md.Write(ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return order.Uint64(ksd.b[:blockSize]), nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readBytes(num uint32) ([]byte, error) {
|
||||
var result []byte
|
||||
for lenToRead := num; lenToRead > 0; {
|
||||
blockSize := lenToRead
|
||||
if blockSize > bufSize {
|
||||
blockSize = bufSize
|
||||
}
|
||||
_, err := io.ReadFull(ksd.r, ksd.b[:blockSize])
|
||||
if err != nil {
|
||||
return result, ErrIo
|
||||
}
|
||||
result = append(result, ksd.b[:blockSize]...)
|
||||
lenToRead -= blockSize
|
||||
}
|
||||
_, err := ksd.md.Write(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readString() (string, error) {
|
||||
strLen, err := ksd.readUint16()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bytes, err := ksd.readBytes(uint32(strLen))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readCertificate(version uint32) (*Certificate, error) {
|
||||
var certType string
|
||||
switch version {
|
||||
case version01:
|
||||
certType = defaultCertificateType
|
||||
case version02:
|
||||
readCertType, err := ksd.readString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certType = readCertType
|
||||
default:
|
||||
return nil, ErrIncorrectVersion
|
||||
}
|
||||
certLen, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certContent, err := ksd.readBytes(certLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificate := Certificate{
|
||||
Type: certType,
|
||||
Content: certContent,
|
||||
}
|
||||
return &certificate, nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readPrivateKeyEntry(version uint32, password []byte) (*PrivateKeyEntry, error) {
|
||||
creationDateTimeStamp, err := ksd.readUint64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privKeyLen, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encodedPrivateKeyContent, err := ksd.readBytes(privKeyLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certCount, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var chain []Certificate
|
||||
for i := certCount; i > 0; i-- {
|
||||
cert, err := ksd.readCertificate(version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chain = append(chain, *cert)
|
||||
}
|
||||
plainPrivateKeyContent, err := recoverKey(encodedPrivateKeyContent, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creationDateTime := millisecondsToTime(int64(creationDateTimeStamp))
|
||||
privateKeyEntry := PrivateKeyEntry{
|
||||
Entry: Entry{
|
||||
CreationDate: creationDateTime,
|
||||
},
|
||||
PrivKey: plainPrivateKeyContent,
|
||||
CertChain: chain,
|
||||
}
|
||||
return &privateKeyEntry, nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readTrustedCertificateEntry(version uint32) (*TrustedCertificateEntry, error) {
|
||||
creationDateTimeStamp, err := ksd.readUint64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := ksd.readCertificate(version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creationDateTime := millisecondsToTime(int64(creationDateTimeStamp))
|
||||
trustedCertificateEntry := TrustedCertificateEntry{
|
||||
Entry: Entry{
|
||||
CreationDate: creationDateTime,
|
||||
},
|
||||
Certificate: *cert,
|
||||
}
|
||||
return &trustedCertificateEntry, nil
|
||||
}
|
||||
|
||||
func (ksd *keyStoreDecoder) readEntry(version uint32, password []byte) (string, interface{}, error) {
|
||||
tag, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
alias, err := ksd.readString()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
switch tag {
|
||||
case privateKeyTag:
|
||||
entry, err := ksd.readPrivateKeyEntry(version, password)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return alias, entry, nil
|
||||
case trustedCertificateTag:
|
||||
entry, err := ksd.readTrustedCertificateEntry(version)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return alias, entry, nil
|
||||
}
|
||||
return "", nil, ErrIncorrectTag
|
||||
}
|
||||
|
||||
// Decode reads keystore representation from r then decrypts and check signature using password
|
||||
// It is strongly recommended to fill password slice with zero after usage
|
||||
func Decode(r io.Reader, password []byte) (KeyStore, error) {
|
||||
ksd := keyStoreDecoder{
|
||||
r: r,
|
||||
md: sha1.New(),
|
||||
}
|
||||
passwordBytes := passwordBytes(password)
|
||||
defer zeroing(passwordBytes)
|
||||
_, err := ksd.md.Write(passwordBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = ksd.md.Write(whitenerMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
readMagic, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if readMagic != magic {
|
||||
return nil, ErrIncorrectMagic
|
||||
}
|
||||
version, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count, err := ksd.readUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyStore := KeyStore{}
|
||||
for entitiesCount := count; entitiesCount > 0; entitiesCount-- {
|
||||
alias, entry, err := ksd.readEntry(version, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyStore[alias] = entry
|
||||
}
|
||||
|
||||
computedDigest := ksd.md.Sum(nil)
|
||||
actualDigest, err := ksd.readBytes(uint32(ksd.md.Size()))
|
||||
for i := 0; i < len(actualDigest); i++ {
|
||||
if actualDigest[i] != computedDigest[i] {
|
||||
return nil, ErrInvalidDigest
|
||||
}
|
||||
}
|
||||
|
||||
return keyStore, nil
|
||||
}
|
436
deps/github.com/pavel-v-chernykh/keystore-go/decoder_test.go
vendored
Normal file
436
deps/github.com/pavel-v-chernykh/keystore-go/decoder_test.go
vendored
Normal file
|
@ -0,0 +1,436 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadUint16(t *testing.T) {
|
||||
type readUint16Item struct {
|
||||
input []byte
|
||||
number uint16
|
||||
err error
|
||||
hash [sha1.Size]byte
|
||||
}
|
||||
var readUint32Table = func() []readUint16Item {
|
||||
var table []readUint16Item
|
||||
table = append(table, readUint16Item{
|
||||
input: nil,
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readUint16Item{
|
||||
input: []byte{},
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readUint16Item{
|
||||
input: []byte{1},
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
buf := make([]byte, 2)
|
||||
var number uint16 = 10
|
||||
binary.BigEndian.PutUint16(buf, number)
|
||||
table = append(table, readUint16Item{
|
||||
input: buf,
|
||||
number: number,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
buf = make([]byte, 2)
|
||||
number = 0
|
||||
binary.BigEndian.PutUint16(buf, number)
|
||||
table = append(table, readUint16Item{
|
||||
input: buf,
|
||||
number: number,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
return table
|
||||
}()
|
||||
|
||||
for _, tt := range readUint32Table {
|
||||
ksd := keyStoreDecoder{
|
||||
r: bytes.NewReader(tt.input),
|
||||
md: sha1.New(),
|
||||
}
|
||||
number, err := ksd.readUint16()
|
||||
hash := ksd.md.Sum(nil)
|
||||
if err != tt.err {
|
||||
t.Errorf("Invalid error '%v' '%v'", err, tt.err)
|
||||
}
|
||||
if number != tt.number {
|
||||
t.Errorf("Invalid uint16 '%v' '%v'", number, tt.number)
|
||||
}
|
||||
if !reflect.DeepEqual(hash, tt.hash[:]) {
|
||||
t.Errorf("Invalid hash '%v' '%v'", hash, tt.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUint32(t *testing.T) {
|
||||
type readUint32Item struct {
|
||||
input []byte
|
||||
number uint32
|
||||
err error
|
||||
hash [sha1.Size]byte
|
||||
}
|
||||
var readUint32Table = func() []readUint32Item {
|
||||
var table []readUint32Item
|
||||
table = append(table, readUint32Item{
|
||||
input: nil,
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readUint32Item{
|
||||
input: []byte{},
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readUint32Item{
|
||||
input: []byte{1, 2, 3},
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
buf := make([]byte, 4)
|
||||
var number uint32 = 10
|
||||
binary.BigEndian.PutUint32(buf, number)
|
||||
table = append(table, readUint32Item{
|
||||
input: buf,
|
||||
number: number,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
buf = make([]byte, 4)
|
||||
number = 0
|
||||
binary.BigEndian.PutUint32(buf, number)
|
||||
table = append(table, readUint32Item{
|
||||
input: buf,
|
||||
number: number,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
return table
|
||||
}()
|
||||
|
||||
for _, tt := range readUint32Table {
|
||||
ksd := keyStoreDecoder{
|
||||
r: bytes.NewReader(tt.input),
|
||||
md: sha1.New(),
|
||||
}
|
||||
number, err := ksd.readUint32()
|
||||
hash := ksd.md.Sum(nil)
|
||||
if err != tt.err {
|
||||
t.Errorf("Invalid error '%v' '%v'", err, tt.err)
|
||||
}
|
||||
if number != tt.number {
|
||||
t.Errorf("Invalid uint32 '%v' '%v'", number, tt.number)
|
||||
}
|
||||
if !reflect.DeepEqual(hash, tt.hash[:]) {
|
||||
t.Errorf("Invalid hash '%v' '%v'", hash, tt.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUint64(t *testing.T) {
|
||||
type readUint64Item struct {
|
||||
input []byte
|
||||
number uint64
|
||||
err error
|
||||
hash [sha1.Size]byte
|
||||
}
|
||||
var readUint64Table = func() []readUint64Item {
|
||||
var table []readUint64Item
|
||||
table = append(table, readUint64Item{
|
||||
input: nil,
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readUint64Item{
|
||||
input: []byte{},
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readUint64Item{
|
||||
input: []byte{1, 2, 3},
|
||||
number: 0,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
buf := make([]byte, 8)
|
||||
var number uint64 = 10
|
||||
binary.BigEndian.PutUint64(buf, number)
|
||||
table = append(table, readUint64Item{
|
||||
input: buf,
|
||||
number: number,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
buf = make([]byte, 8)
|
||||
number = 0
|
||||
binary.BigEndian.PutUint64(buf, number)
|
||||
table = append(table, readUint64Item{
|
||||
input: buf,
|
||||
number: number,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
return table
|
||||
}()
|
||||
|
||||
for _, tt := range readUint64Table {
|
||||
ksd := keyStoreDecoder{
|
||||
r: bytes.NewReader(tt.input),
|
||||
md: sha1.New(),
|
||||
}
|
||||
number, err := ksd.readUint64()
|
||||
hash := ksd.md.Sum(nil)
|
||||
|
||||
if err != tt.err {
|
||||
t.Errorf("Invalid error '%v' '%v'", err, tt.err)
|
||||
}
|
||||
if number != tt.number {
|
||||
t.Errorf("Invalid uint64 '%v' '%v'", number, tt.number)
|
||||
}
|
||||
if !reflect.DeepEqual(hash, tt.hash[:]) {
|
||||
t.Errorf("Invalid hash '%v' '%v'", hash, tt.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadBytes(t *testing.T) {
|
||||
type readBytesItem struct {
|
||||
input []byte
|
||||
readLen uint32
|
||||
bytes []byte
|
||||
err error
|
||||
hash [sha1.Size]byte
|
||||
}
|
||||
var readUint32Table = func() []readBytesItem {
|
||||
var table []readBytesItem
|
||||
table = append(table, readBytesItem{
|
||||
input: nil,
|
||||
readLen: 0,
|
||||
bytes: nil,
|
||||
err: nil,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readBytesItem{
|
||||
input: []byte{1, 2, 3},
|
||||
readLen: 3,
|
||||
bytes: []byte{1, 2, 3},
|
||||
err: nil,
|
||||
hash: sha1.Sum([]byte{1, 2, 3}),
|
||||
})
|
||||
table = append(table, readBytesItem{
|
||||
input: []byte{1, 2, 3},
|
||||
readLen: 2,
|
||||
bytes: []byte{1, 2},
|
||||
err: nil,
|
||||
hash: sha1.Sum([]byte{1, 2}),
|
||||
})
|
||||
buf := func() []byte {
|
||||
buf := make([]byte, 10*1024)
|
||||
_, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
return buf
|
||||
}()
|
||||
table = append(table, readBytesItem{
|
||||
input: buf,
|
||||
readLen: 9 * 1024,
|
||||
bytes: buf[:9*1024],
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf[:9*1024]),
|
||||
})
|
||||
return table
|
||||
}()
|
||||
|
||||
for _, tt := range readUint32Table {
|
||||
ksd := keyStoreDecoder{
|
||||
r: bytes.NewReader(tt.input),
|
||||
md: sha1.New(),
|
||||
}
|
||||
bts, err := ksd.readBytes(tt.readLen)
|
||||
hash := ksd.md.Sum(nil)
|
||||
if err != tt.err {
|
||||
t.Errorf("Invalid error '%v' '%v'", err, tt.err)
|
||||
}
|
||||
if !reflect.DeepEqual(bts, tt.bytes) {
|
||||
t.Errorf("Invalid bytes '%v' '%v'", bts, tt.bytes)
|
||||
}
|
||||
if !reflect.DeepEqual(hash, tt.hash[:]) {
|
||||
t.Errorf("Invalid hash '%v' '%v'", hash, tt.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadString(t *testing.T) {
|
||||
type readStringItem struct {
|
||||
input []byte
|
||||
string string
|
||||
err error
|
||||
hash [sha1.Size]byte
|
||||
}
|
||||
var readUint32Table = func() []readStringItem {
|
||||
var table []readStringItem
|
||||
table = append(table, readStringItem{
|
||||
input: nil,
|
||||
string: "",
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readStringItem{
|
||||
input: []byte{},
|
||||
string: "",
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readStringItem{
|
||||
input: []byte{1, 2, 3},
|
||||
string: "",
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum([]byte{1, 2}),
|
||||
})
|
||||
str := "some string to read"
|
||||
buf := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(buf, uint16(len(str)))
|
||||
buf = append(buf, []byte(str)...)
|
||||
table = append(table, readStringItem{
|
||||
input: buf,
|
||||
string: str,
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
})
|
||||
return table
|
||||
}()
|
||||
|
||||
for _, tt := range readUint32Table {
|
||||
ksd := keyStoreDecoder{
|
||||
r: bytes.NewReader(tt.input),
|
||||
md: sha1.New(),
|
||||
}
|
||||
str, err := ksd.readString()
|
||||
hash := ksd.md.Sum(nil)
|
||||
if err != tt.err {
|
||||
t.Errorf("Invalid error '%v' '%v'", err, tt.err)
|
||||
}
|
||||
if str != tt.string {
|
||||
t.Errorf("Invalid string '%v' '%v'", str, tt.string)
|
||||
}
|
||||
if !reflect.DeepEqual(hash, tt.hash[:]) {
|
||||
t.Errorf("Invalid hash '%v' '%v'", hash, tt.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadCertificate(t *testing.T) {
|
||||
type readCertificateItem struct {
|
||||
input []byte
|
||||
version uint32
|
||||
cert *Certificate
|
||||
err error
|
||||
hash [sha1.Size]byte
|
||||
}
|
||||
|
||||
var readCertificateTable = func() []readCertificateItem {
|
||||
var table []readCertificateItem
|
||||
table = append(table, readCertificateItem{
|
||||
input: nil,
|
||||
version: version01,
|
||||
cert: nil,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readCertificateItem{
|
||||
input: nil,
|
||||
version: version02,
|
||||
cert: nil,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, readCertificateItem{
|
||||
input: nil,
|
||||
version: 3,
|
||||
cert: nil,
|
||||
err: ErrIncorrectVersion,
|
||||
hash: sha1.Sum(nil),
|
||||
})
|
||||
table = append(table, func() readCertificateItem {
|
||||
input := []byte{0, 0, 0, 0}
|
||||
return readCertificateItem{
|
||||
input: input,
|
||||
version: version01,
|
||||
cert: &Certificate{
|
||||
Type: defaultCertificateType,
|
||||
Content: nil,
|
||||
},
|
||||
err: nil,
|
||||
hash: sha1.Sum(input),
|
||||
}
|
||||
}())
|
||||
table = append(table, func() readCertificateItem {
|
||||
buf := make([]byte, 2)
|
||||
order.PutUint16(buf, uint16(len(defaultCertificateType)))
|
||||
buf = append(buf, []byte(defaultCertificateType)...)
|
||||
buf = append(buf, 0, 0, 0, 0)
|
||||
return readCertificateItem{
|
||||
input: buf,
|
||||
version: version02,
|
||||
cert: &Certificate{
|
||||
Type: defaultCertificateType,
|
||||
Content: nil,
|
||||
},
|
||||
err: nil,
|
||||
hash: sha1.Sum(buf),
|
||||
}
|
||||
}())
|
||||
table = append(table, func() readCertificateItem {
|
||||
buf := make([]byte, 2)
|
||||
order.PutUint16(buf, uint16(len(defaultCertificateType)))
|
||||
buf = append(buf, []byte(defaultCertificateType)...)
|
||||
buf = append(buf, 0, 0, 0, 1)
|
||||
return readCertificateItem{
|
||||
input: buf,
|
||||
version: version02,
|
||||
cert: nil,
|
||||
err: ErrIo,
|
||||
hash: sha1.Sum(buf),
|
||||
}
|
||||
}())
|
||||
|
||||
return table
|
||||
}()
|
||||
|
||||
for _, tt := range readCertificateTable {
|
||||
ksd := keyStoreDecoder{
|
||||
r: bytes.NewReader(tt.input),
|
||||
md: sha1.New(),
|
||||
}
|
||||
cert, err := ksd.readCertificate(tt.version)
|
||||
hash := ksd.md.Sum(nil)
|
||||
if err != tt.err {
|
||||
t.Errorf("Invalid error '%v' '%v'", err, tt.err)
|
||||
}
|
||||
if cert != nil && tt.cert != nil && !reflect.DeepEqual(cert, tt.cert) {
|
||||
t.Errorf("Invalid certificate '%v' '%v'", cert, tt.cert)
|
||||
}
|
||||
if !reflect.DeepEqual(hash, tt.hash[:]) {
|
||||
t.Errorf("Invalid hash '%v' '%v'", hash, tt.hash)
|
||||
}
|
||||
}
|
||||
}
|
241
deps/github.com/pavel-v-chernykh/keystore-go/encoder.go
vendored
Normal file
241
deps/github.com/pavel-v-chernykh/keystore-go/encoder.go
vendored
Normal file
|
@ -0,0 +1,241 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// ErrEncodedSequenceTooLong indicates that size of string or bytes trying to encode too big
|
||||
var ErrEncodedSequenceTooLong = errors.New("keystore: encoded sequence too long")
|
||||
|
||||
// ErrIncorrectEntryType indicates incorrect entry type addressing
|
||||
var ErrIncorrectEntryType = errors.New("keystore: incorrect entry type")
|
||||
|
||||
type keyStoreEncoder struct {
|
||||
w io.Writer
|
||||
b [bufSize]byte
|
||||
md hash.Hash
|
||||
rand io.Reader
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeUint16(value uint16) error {
|
||||
const blockSize = 2
|
||||
order.PutUint16(kse.b[:blockSize], value)
|
||||
_, err := kse.w.Write(kse.b[:blockSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = kse.md.Write(kse.b[:blockSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeUint32(value uint32) error {
|
||||
const blockSize = 4
|
||||
order.PutUint32(kse.b[:blockSize], value)
|
||||
_, err := kse.w.Write(kse.b[:blockSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = kse.md.Write(kse.b[:blockSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeUint64(value uint64) error {
|
||||
const blockSize = 8
|
||||
order.PutUint64(kse.b[:blockSize], value)
|
||||
_, err := kse.w.Write(kse.b[:blockSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = kse.md.Write(kse.b[:blockSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeBytes(value []byte) error {
|
||||
_, err := kse.w.Write(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = kse.md.Write(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeString(value string) error {
|
||||
strLen := len(value)
|
||||
if strLen > math.MaxUint16 {
|
||||
return ErrEncodedSequenceTooLong
|
||||
}
|
||||
err := kse.writeUint16(uint16(strLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeBytes([]byte(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeCertificate(cert *Certificate) error {
|
||||
err := kse.writeString(cert.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certLen := uint64(len(cert.Content))
|
||||
if certLen > math.MaxUint32 {
|
||||
return ErrEncodedSequenceTooLong
|
||||
}
|
||||
err = kse.writeUint32(uint32(certLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeBytes(cert.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writeTrustedCertificateEntry(alias string, tce *TrustedCertificateEntry) error {
|
||||
err := kse.writeUint32(trustedCertificateTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeString(alias)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeUint64(uint64(timeToMilliseconds(tce.CreationDate)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeCertificate(&tce.Certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kse *keyStoreEncoder) writePrivateKeyEntry(alias string, pke *PrivateKeyEntry, password []byte) error {
|
||||
err := kse.writeUint32(privateKeyTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeString(alias)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeUint64(uint64(timeToMilliseconds(pke.CreationDate)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encodedPrivKeyContent, err := protectKey(kse.rand, pke.PrivKey, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKeyLen := uint64(len(encodedPrivKeyContent))
|
||||
if privKeyLen > math.MaxUint32 {
|
||||
return ErrEncodedSequenceTooLong
|
||||
}
|
||||
err = kse.writeUint32(uint32(privKeyLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeBytes(encodedPrivKeyContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certCount := uint64(len(pke.CertChain))
|
||||
if certCount > math.MaxUint32 {
|
||||
return ErrEncodedSequenceTooLong
|
||||
}
|
||||
err = kse.writeUint32(uint32(certCount))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cert := range pke.CertChain {
|
||||
err = kse.writeCertificate(&cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encrypts and signs keystore using password and writes its representation into w
|
||||
// It is strongly recommended to fill password slice with zero after usage
|
||||
func Encode(w io.Writer, ks KeyStore, password []byte) error {
|
||||
return EncodeWithRand(rand.Reader, w, ks, password)
|
||||
}
|
||||
|
||||
// Encode encrypts and signs keystore using password and writes its representation into w
|
||||
// Random bytes are read from rand, which must be a cryptographically secure source of randomness
|
||||
// It is strongly recommended to fill password slice with zero after usage
|
||||
func EncodeWithRand(rand io.Reader, w io.Writer, ks KeyStore, password []byte) error {
|
||||
kse := keyStoreEncoder{
|
||||
w: w,
|
||||
md: sha1.New(),
|
||||
rand: rand,
|
||||
}
|
||||
passwordBytes := passwordBytes(password)
|
||||
defer zeroing(passwordBytes)
|
||||
_, err := kse.md.Write(passwordBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = kse.md.Write(whitenerMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kse.writeUint32(magic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// always write latest version
|
||||
err = kse.writeUint32(version02)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kse.writeUint32(uint32(len(ks)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for alias, entry := range ks {
|
||||
switch typedEntry := entry.(type) {
|
||||
case *PrivateKeyEntry:
|
||||
err = kse.writePrivateKeyEntry(alias, typedEntry, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case *TrustedCertificateEntry:
|
||||
err = kse.writeTrustedCertificateEntry(alias, typedEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrIncorrectEntryType
|
||||
}
|
||||
}
|
||||
err = kse.writeBytes(kse.md.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
BIN
deps/github.com/pavel-v-chernykh/keystore-go/examples/compare/keystore.jks
vendored
Normal file
BIN
deps/github.com/pavel-v-chernykh/keystore-go/examples/compare/keystore.jks
vendored
Normal file
Binary file not shown.
53
deps/github.com/pavel-v-chernykh/keystore-go/examples/compare/main.go
vendored
Normal file
53
deps/github.com/pavel-v-chernykh/keystore-go/examples/compare/main.go
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pavel-v-chernykh/keystore-go"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func readKeyStore(filename string, password []byte) keystore.KeyStore {
|
||||
f, err := os.Open(filename)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
keyStore, err := keystore.Decode(f, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return keyStore
|
||||
}
|
||||
|
||||
func writeKeyStore(keyStore keystore.KeyStore, filename string, password []byte) {
|
||||
o, err := os.Create(filename)
|
||||
defer o.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = keystore.Encode(o, keyStore, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func zeroing(s []byte) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
password := []byte{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}
|
||||
defer zeroing(password)
|
||||
ks1 := readKeyStore("keystore.jks", password)
|
||||
|
||||
writeKeyStore(ks1, "keystore2.jks", password)
|
||||
|
||||
ks2 := readKeyStore("keystore2.jks", password)
|
||||
|
||||
log.Printf("Is equal: %v\n", reflect.DeepEqual(ks1, ks2))
|
||||
}
|
83
deps/github.com/pavel-v-chernykh/keystore-go/examples/pem/main.go
vendored
Normal file
83
deps/github.com/pavel-v-chernykh/keystore-go/examples/pem/main.go
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"github.com/pavel-v-chernykh/keystore-go"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func readKeyStore(filename string, password []byte) keystore.KeyStore {
|
||||
f, err := os.Open(filename)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
keyStore, err := keystore.Decode(f, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return keyStore
|
||||
}
|
||||
|
||||
func writeKeyStore(keyStore keystore.KeyStore, filename string, password []byte) {
|
||||
o, err := os.Create(filename)
|
||||
defer o.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = keystore.Encode(o, keyStore, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func zeroing(s []byte) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// openssl genrsa 1024 | openssl pkcs8 -topk8 -inform pem -outform pem -nocrypt -out privkey.pem
|
||||
pke, err := ioutil.ReadFile("./privkey.pem")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
p, _ := pem.Decode(pke)
|
||||
if p == nil {
|
||||
log.Fatal("Should have at least one pem block")
|
||||
}
|
||||
if p.Type != "PRIVATE KEY" {
|
||||
log.Fatal("Should be a rsa private key")
|
||||
}
|
||||
|
||||
keyStore := keystore.KeyStore{
|
||||
"alias": &keystore.PrivateKeyEntry{
|
||||
Entry: keystore.Entry{
|
||||
CreationDate: time.Now(),
|
||||
},
|
||||
PrivKey: p.Bytes,
|
||||
},
|
||||
}
|
||||
|
||||
password := []byte{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}
|
||||
defer zeroing(password)
|
||||
writeKeyStore(keyStore, "keystore.jks", password)
|
||||
|
||||
ks := readKeyStore("keystore.jks", password)
|
||||
|
||||
entry := ks["alias"]
|
||||
privKeyEntry := entry.(*keystore.PrivateKeyEntry)
|
||||
key, err := x509.ParsePKCS8PrivateKey(privKeyEntry.PrivKey)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("%v", key)
|
||||
}
|
16
deps/github.com/pavel-v-chernykh/keystore-go/examples/pem/privkey.pem
vendored
Normal file
16
deps/github.com/pavel-v-chernykh/keystore-go/examples/pem/privkey.pem
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMiiH2T1BHnHiaU8
|
||||
EGAIa6JHkOZCfb9xDLyfqL73m/kFTFAJAhJ9gGusVIk5NCS0aGgASsNnGiqmhFbE
|
||||
MiYuvxRzUJIKL9hh1ttnGhyZQ0hwlyeTNVJKCWNX6rcLZ+blO7kpF6YI6fqYsmWc
|
||||
QeyxgW2NAFH9HaiqF5F0V0DUXh4dAgMBAAECgYEAwRS0ndXmbsQGxUuefqzb2JqC
|
||||
6fWHSpujJEuKe+2S3v2oSUXCBsVct0JrQHwaoFA2QhA14wLv/aeuqEm78V7/ZxsF
|
||||
vi+PFsYGsag05N83vZcJi/fHbLzkWFOANAnr7i/4u1sd2fIqFkm5xY5lw02lW5JN
|
||||
daVDAo/njAEsTYn2OEkCQQD3cx/50LWmsQnsmSy4RdfERYWMuxDjSlvixfabEb3X
|
||||
usyZ8CcK1RUUdEi7m+H+3KXJvuNaZiQ5WdQ7nXX34plXAkEAz5DfPLcC40sQlXxM
|
||||
G0pteLNDemV81Okj4yfzukRZXwt54JjPd0AO1GQbmB4K8Zqag7p0ekU/5Y7oL+JQ
|
||||
fXA3qwJAPudRNZxM0TcoIrE9oQqAMzDJJmFXhbAdc6SHcBwuemzOHkPiaOqKFU0K
|
||||
QEb8SGGm84ZHHW/hvYKMZSs+FenQuQJBAMC6T83cUH4j0P48L56XeRY9vUYEvegj
|
||||
ogLlsdUeaa1qxnvY56pefGaRnV2dZ6P2Xco6crSlYDMSgl0T0pDmhYkCQDZ1dn3F
|
||||
+MXxmJT9uRSfwLj4cNPHICZKrlp20lqDYvEZtIiouY4c/Y7cYykUSotzUgigng9D
|
||||
zK3idGFIejk+Ezo=
|
||||
-----END PRIVATE KEY-----
|
165
deps/github.com/pavel-v-chernykh/keystore-go/keyprotector.go
vendored
Normal file
165
deps/github.com/pavel-v-chernykh/keystore-go/keyprotector.go
vendored
Normal file
|
@ -0,0 +1,165 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const saltLen = 20
|
||||
|
||||
var supportedPrivateKeyAlgorithmOid = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 42, 2, 17, 1, 1})
|
||||
|
||||
// ErrUnsupportedPrivateKeyAlgorithm indicates unsupported private key algorithm
|
||||
var ErrUnsupportedPrivateKeyAlgorithm = errors.New("keystore: unsupported private key algorithm")
|
||||
|
||||
// ErrUnrecoverablePrivateKey indicates unrecoverable private key content (often means wrong password usage)
|
||||
var ErrUnrecoverablePrivateKey = errors.New("keystore: unrecoverable private key")
|
||||
|
||||
type keyInfo struct {
|
||||
Algo pkix.AlgorithmIdentifier
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
func recoverKey(encodedKey []byte, password []byte) ([]byte, error) {
|
||||
var keyInfo keyInfo
|
||||
asn1Rest, err := asn1.Unmarshal(encodedKey, &keyInfo)
|
||||
if err != nil || len(asn1Rest) > 0 {
|
||||
return nil, ErrIncorrectPrivateKey
|
||||
}
|
||||
if !keyInfo.Algo.Algorithm.Equal(supportedPrivateKeyAlgorithmOid) {
|
||||
return nil, ErrUnsupportedPrivateKeyAlgorithm
|
||||
}
|
||||
|
||||
md := sha1.New()
|
||||
passwordBytes := passwordBytes(password)
|
||||
defer zeroing(passwordBytes)
|
||||
salt := make([]byte, saltLen)
|
||||
copy(salt, keyInfo.PrivateKey)
|
||||
encrKeyLen := len(keyInfo.PrivateKey) - saltLen - md.Size()
|
||||
numRounds := encrKeyLen / md.Size()
|
||||
|
||||
if encrKeyLen%md.Size() != 0 {
|
||||
numRounds++
|
||||
}
|
||||
|
||||
encrKey := make([]byte, encrKeyLen)
|
||||
copy(encrKey, keyInfo.PrivateKey[saltLen:])
|
||||
|
||||
xorKey := make([]byte, encrKeyLen)
|
||||
|
||||
digest := salt
|
||||
for i, xorOffset := 0, 0; i < numRounds; i++ {
|
||||
_, err := md.Write(passwordBytes)
|
||||
if err != nil {
|
||||
return nil, ErrUnrecoverablePrivateKey
|
||||
}
|
||||
_, err = md.Write(digest)
|
||||
if err != nil {
|
||||
return nil, ErrUnrecoverablePrivateKey
|
||||
}
|
||||
digest = md.Sum(nil)
|
||||
md.Reset()
|
||||
copy(xorKey[xorOffset:], digest)
|
||||
xorOffset += md.Size()
|
||||
}
|
||||
|
||||
plainKey := make([]byte, encrKeyLen)
|
||||
for i := 0; i < len(plainKey); i++ {
|
||||
plainKey[i] = encrKey[i] ^ xorKey[i]
|
||||
}
|
||||
|
||||
_, err = md.Write(passwordBytes)
|
||||
if err != nil {
|
||||
return nil, ErrUnrecoverablePrivateKey
|
||||
}
|
||||
_, err = md.Write(plainKey)
|
||||
if err != nil {
|
||||
return nil, ErrUnrecoverablePrivateKey
|
||||
}
|
||||
digest = md.Sum(nil)
|
||||
md.Reset()
|
||||
|
||||
digestOffset := saltLen + encrKeyLen
|
||||
for i := 0; i < len(digest); i++ {
|
||||
if digest[i] != keyInfo.PrivateKey[digestOffset+i] {
|
||||
return nil, ErrUnrecoverablePrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
return plainKey, nil
|
||||
}
|
||||
|
||||
func protectKey(rand io.Reader, plainKey []byte, password []byte) ([]byte, error) {
|
||||
md := sha1.New()
|
||||
passwdBytes := passwordBytes(password)
|
||||
defer zeroing(passwdBytes)
|
||||
plainKeyLen := len(plainKey)
|
||||
numRounds := plainKeyLen / md.Size()
|
||||
|
||||
if plainKeyLen%md.Size() != 0 {
|
||||
numRounds++
|
||||
}
|
||||
|
||||
salt := make([]byte, saltLen)
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xorKey := make([]byte, plainKeyLen)
|
||||
|
||||
digest := salt
|
||||
for i, xorOffset := 0, 0; i < numRounds; i++ {
|
||||
_, err = md.Write(passwdBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = md.Write(digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest = md.Sum(nil)
|
||||
md.Reset()
|
||||
copy(xorKey[xorOffset:], digest)
|
||||
xorOffset += md.Size()
|
||||
}
|
||||
|
||||
tmpKey := make([]byte, plainKeyLen)
|
||||
for i := 0; i < plainKeyLen; i++ {
|
||||
tmpKey[i] = plainKey[i] ^ xorKey[i]
|
||||
}
|
||||
|
||||
encrKey := make([]byte, saltLen+plainKeyLen+md.Size())
|
||||
encrKeyOffset := 0
|
||||
copy(encrKey[encrKeyOffset:], salt)
|
||||
encrKeyOffset += saltLen
|
||||
copy(encrKey[encrKeyOffset:], tmpKey)
|
||||
encrKeyOffset += plainKeyLen
|
||||
|
||||
_, err = md.Write(passwdBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = md.Write(plainKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest = md.Sum(nil)
|
||||
md.Reset()
|
||||
copy(encrKey[encrKeyOffset:], digest)
|
||||
keyInfo := keyInfo{
|
||||
Algo: pkix.AlgorithmIdentifier{
|
||||
Algorithm: supportedPrivateKeyAlgorithmOid,
|
||||
Parameters: asn1.RawValue{Tag: 5},
|
||||
},
|
||||
PrivateKey: encrKey,
|
||||
}
|
||||
encodedKey, err := asn1.Marshal(keyInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encodedKey, nil
|
||||
}
|
32
deps/github.com/pavel-v-chernykh/keystore-go/keystore.go
vendored
Normal file
32
deps/github.com/pavel-v-chernykh/keystore-go/keystore.go
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// KeyStore is a mapping of alias to pointer to PrivateKeyEntry or TrustedCertificateEntry
|
||||
type KeyStore map[string]interface{}
|
||||
|
||||
// Certificate describes type of certificate
|
||||
type Certificate struct {
|
||||
Type string
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// Entry is a basis of entries types supported by keystore
|
||||
type Entry struct {
|
||||
CreationDate time.Time
|
||||
}
|
||||
|
||||
// PrivateKeyEntry is an entry for private keys and associated certificates
|
||||
type PrivateKeyEntry struct {
|
||||
Entry
|
||||
PrivKey []byte
|
||||
CertChain []Certificate
|
||||
}
|
||||
|
||||
// TrustedCertificateEntry is an entry for certificates only
|
||||
type TrustedCertificateEntry struct {
|
||||
Entry
|
||||
Certificate Certificate
|
||||
}
|
|
@ -8,4 +8,5 @@
|
|||
- [Services & Load balancer](./services_and_loadbalancer.md)
|
||||
- [Storage](./storage.md)
|
||||
- [Storage Resource](./storage_resource.md)
|
||||
- [TLS](./tls.md)
|
||||
- [Upgrading](./upgrading.md)
|
||||
|
|
42
docs/user/tls.md
Normal file
42
docs/user/tls.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# TLS
|
||||
|
||||
The ArangoDB operator allows you to create ArangoDB deployments that use
|
||||
secure TLS connections.
|
||||
|
||||
It uses a single CA certificate (stored in a Kubernetes secret) and
|
||||
one certificate per ArangoDB server (stored in a Kubernetes secret per server).
|
||||
|
||||
## Install CA certificate
|
||||
|
||||
If the CA certificate is self-signed, it will not be trusted by browsers,
|
||||
until you install it in the local operating system or browser.
|
||||
This process differs per operating system.
|
||||
|
||||
To do so, you first have to fetch the CA certificate from its Kubernetes
|
||||
secret.
|
||||
|
||||
```bash
|
||||
kubectl get secret <deploy-name> --template='{{index .data "ca.crt"}}' | base64 -D > ca.crt
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
TODO
|
||||
|
||||
### MacOS
|
||||
|
||||
To install a CA certificate in MacOS, run:
|
||||
|
||||
```bash
|
||||
sudo /usr/bin/security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt
|
||||
```
|
||||
|
||||
To uninstall a CA certificate in MacOS, run:
|
||||
|
||||
```bash
|
||||
sudo /usr/bin/security remove-trusted-cert -d ca.crt
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
TODO
|
9
examples/simple-cluster-tls.yaml
Normal file
9
examples/simple-cluster-tls.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
apiVersion: "database.arangodb.com/v1alpha"
|
||||
kind: "ArangoDeployment"
|
||||
metadata:
|
||||
name: "example-simple-cluster-tls"
|
||||
spec:
|
||||
mode: cluster
|
||||
tls:
|
||||
caSecretName: example-simple-cluster-tls
|
||||
altNames: ["kube-01", "kube-02", "kube-03"]
|
|
@ -52,7 +52,7 @@ type DeploymentSpec struct {
|
|||
|
||||
RocksDB RocksDBSpec `json:"rocksdb"`
|
||||
Authentication AuthenticationSpec `json:"auth"`
|
||||
SSL SSLSpec `json:"ssl"`
|
||||
TLS TLSSpec `json:"tls"`
|
||||
Sync SyncSpec `json:"sync"`
|
||||
|
||||
Single ServerGroupSpec `json:"single"`
|
||||
|
@ -70,7 +70,7 @@ func (s DeploymentSpec) IsAuthenticated() bool {
|
|||
|
||||
// IsSecure returns true when SSL is enabled
|
||||
func (s DeploymentSpec) IsSecure() bool {
|
||||
return s.SSL.IsSecure()
|
||||
return s.TLS.IsSecure()
|
||||
}
|
||||
|
||||
// SetDefaults fills in default values when a field is not specified.
|
||||
|
@ -92,8 +92,8 @@ func (s *DeploymentSpec) SetDefaults(deploymentName string) {
|
|||
}
|
||||
s.RocksDB.SetDefaults()
|
||||
s.Authentication.SetDefaults(deploymentName + "-jwt")
|
||||
s.SSL.SetDefaults()
|
||||
s.Sync.SetDefaults(s.Image, s.ImagePullPolicy, deploymentName+"-sync-jwt")
|
||||
s.TLS.SetDefaults("")
|
||||
s.Sync.SetDefaults(s.Image, s.ImagePullPolicy, deploymentName+"-sync-jwt", deploymentName+"-sync-ca")
|
||||
s.Single.SetDefaults(ServerGroupSingle, s.Mode.HasSingleServers(), s.Mode)
|
||||
s.Agents.SetDefaults(ServerGroupAgents, s.Mode.HasAgents(), s.Mode)
|
||||
s.DBServers.SetDefaults(ServerGroupDBServers, s.Mode.HasDBServers(), s.Mode)
|
||||
|
@ -126,8 +126,8 @@ func (s *DeploymentSpec) Validate() error {
|
|||
if err := s.Authentication.Validate(false); err != nil {
|
||||
return maskAny(errors.Wrap(err, "spec.auth"))
|
||||
}
|
||||
if err := s.SSL.Validate(); err != nil {
|
||||
return maskAny(errors.Wrap(err, "spec.ssl"))
|
||||
if err := s.TLS.Validate(); err != nil {
|
||||
return maskAny(errors.Wrap(err, "spec.tls"))
|
||||
}
|
||||
if err := s.Sync.Validate(s.Mode); err != nil {
|
||||
return maskAny(errors.Wrap(err, "spec.sync"))
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package v1alpha
|
||||
|
||||
import (
|
||||
"github.com/arangodb/k8s-operator/pkg/util/k8sutil"
|
||||
)
|
||||
|
||||
// SSLSpec holds SSL specific configuration settings
|
||||
type SSLSpec struct {
|
||||
KeySecretName string `json:"keySecretName,omitempty"`
|
||||
OrganizationName string `json:"organizationName,omitempty"`
|
||||
ServerName string `json:"serverName,omitempty"`
|
||||
}
|
||||
|
||||
// IsSecure returns true when a key secret has been set, false otherwise.
|
||||
func (s SSLSpec) IsSecure() bool {
|
||||
return s.KeySecretName != ""
|
||||
}
|
||||
|
||||
// Validate the given spec
|
||||
func (s SSLSpec) Validate() error {
|
||||
if err := k8sutil.ValidateOptionalResourceName(s.KeySecretName); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDefaults fills in missing defaults
|
||||
func (s *SSLSpec) SetDefaults() {
|
||||
if s.OrganizationName == "" {
|
||||
s.OrganizationName = "ArangoDB"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package v1alpha
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSSLSpecValidate(t *testing.T) {
|
||||
// Valid
|
||||
assert.Nil(t, SSLSpec{KeySecretName: ""}.Validate())
|
||||
assert.Nil(t, SSLSpec{KeySecretName: "foo"}.Validate())
|
||||
|
||||
// Not valid
|
||||
assert.Error(t, SSLSpec{KeySecretName: "Foo"}.Validate())
|
||||
}
|
||||
|
||||
func TestSSLSpecIsSecure(t *testing.T) {
|
||||
assert.False(t, SSLSpec{KeySecretName: ""}.IsSecure())
|
||||
assert.True(t, SSLSpec{KeySecretName: "foo"}.IsSecure())
|
||||
}
|
||||
|
||||
func TestSSLSpecSetDefaults(t *testing.T) {
|
||||
def := func(spec SSLSpec) SSLSpec {
|
||||
spec.SetDefaults()
|
||||
return spec
|
||||
}
|
||||
|
||||
assert.Equal(t, "", def(SSLSpec{}).KeySecretName)
|
||||
assert.Equal(t, "foo", def(SSLSpec{KeySecretName: "foo"}).KeySecretName)
|
||||
assert.Equal(t, "ArangoDB", def(SSLSpec{}).OrganizationName)
|
||||
assert.Equal(t, "foo", def(SSLSpec{OrganizationName: "foo"}).OrganizationName)
|
||||
assert.Equal(t, "", def(SSLSpec{}).ServerName)
|
||||
assert.Equal(t, "foo", def(SSLSpec{ServerName: "foo"}).ServerName)
|
||||
}
|
|
@ -34,6 +34,7 @@ type SyncSpec struct {
|
|||
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty"`
|
||||
|
||||
Authentication AuthenticationSpec `json:"auth"`
|
||||
TLS TLSSpec `json:"tls"`
|
||||
Monitoring MonitoringSpec `json:"monitoring"`
|
||||
}
|
||||
|
||||
|
@ -48,6 +49,11 @@ func (s SyncSpec) Validate(mode DeploymentMode) error {
|
|||
if err := s.Authentication.Validate(s.Enabled); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
if s.Enabled {
|
||||
if err := s.TLS.Validate(); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
}
|
||||
if err := s.Monitoring.Validate(); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
|
@ -55,7 +61,7 @@ func (s SyncSpec) Validate(mode DeploymentMode) error {
|
|||
}
|
||||
|
||||
// SetDefaults fills in missing defaults
|
||||
func (s *SyncSpec) SetDefaults(defaultImage string, defaulPullPolicy v1.PullPolicy, defaultJWTSecretName string) {
|
||||
func (s *SyncSpec) SetDefaults(defaultImage string, defaulPullPolicy v1.PullPolicy, defaultJWTSecretName, defaultCASecretName string) {
|
||||
if s.Image == "" {
|
||||
s.Image = defaultImage
|
||||
}
|
||||
|
@ -63,6 +69,7 @@ func (s *SyncSpec) SetDefaults(defaultImage string, defaulPullPolicy v1.PullPoli
|
|||
s.ImagePullPolicy = defaulPullPolicy
|
||||
}
|
||||
s.Authentication.SetDefaults(defaultJWTSecretName)
|
||||
s.TLS.SetDefaults(defaultCASecretName)
|
||||
s.Monitoring.SetDefaults()
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestSyncSpecValidate(t *testing.T) {
|
|||
|
||||
func TestSyncSpecSetDefaults(t *testing.T) {
|
||||
def := func(spec SyncSpec) SyncSpec {
|
||||
spec.SetDefaults("test-image", v1.PullAlways, "test-jwt")
|
||||
spec.SetDefaults("test-image", v1.PullAlways, "test-jwt", "test-ca")
|
||||
return spec
|
||||
}
|
||||
|
||||
|
|
86
pkg/apis/arangodb/v1alpha/tls_spec.go
Normal file
86
pkg/apis/arangodb/v1alpha/tls_spec.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package v1alpha
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/k8s-operator/pkg/util/k8sutil"
|
||||
"github.com/arangodb/k8s-operator/pkg/util/validation"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTLSTTL = time.Hour * 2160 // About 3 month
|
||||
)
|
||||
|
||||
// TLSSpec holds TLS specific configuration settings
|
||||
type TLSSpec struct {
|
||||
CASecretName string `json:"caSecretName,omitempty"`
|
||||
AltNames []string `json:"altNames,omitempty"`
|
||||
TTL time.Duration `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// IsSecure returns true when a CA secret has been set, false otherwise.
|
||||
func (s TLSSpec) IsSecure() bool {
|
||||
return s.CASecretName != ""
|
||||
}
|
||||
|
||||
// GetAltNames splits the list of AltNames into DNS names, IP addresses & email addresses.
|
||||
// When an entry is not valid for any of those categories, an error is returned.
|
||||
func (s TLSSpec) GetAltNames() (dnsNames, ipAddresses, emailAddresses []string, err error) {
|
||||
for _, name := range s.AltNames {
|
||||
if net.ParseIP(name) != nil {
|
||||
ipAddresses = append(ipAddresses, name)
|
||||
} else if validation.IsValidDNSName(name) {
|
||||
dnsNames = append(dnsNames, name)
|
||||
} else if validation.IsValidEmailAddress(name) {
|
||||
emailAddresses = append(emailAddresses, name)
|
||||
} else {
|
||||
return nil, nil, nil, maskAny(fmt.Errorf("'%s' is not a valid alternate name", name))
|
||||
}
|
||||
}
|
||||
return dnsNames, ipAddresses, emailAddresses, nil
|
||||
}
|
||||
|
||||
// Validate the given spec
|
||||
func (s TLSSpec) Validate() error {
|
||||
if err := k8sutil.ValidateOptionalResourceName(s.CASecretName); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
if _, _, _, err := s.GetAltNames(); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDefaults fills in missing defaults
|
||||
func (s *TLSSpec) SetDefaults(defaultCASecretName string) {
|
||||
if s.CASecretName == "" {
|
||||
s.CASecretName = defaultCASecretName
|
||||
}
|
||||
if s.TTL == 0 {
|
||||
s.TTL = defaultTLSTTL
|
||||
}
|
||||
}
|
62
pkg/apis/arangodb/v1alpha/tls_spec_test.go
Normal file
62
pkg/apis/arangodb/v1alpha/tls_spec_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package v1alpha
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTLSSpecValidate(t *testing.T) {
|
||||
// Valid
|
||||
assert.Nil(t, TLSSpec{CASecretName: ""}.Validate())
|
||||
assert.Nil(t, TLSSpec{CASecretName: "foo"}.Validate())
|
||||
assert.Nil(t, TLSSpec{AltNames: []string{}}.Validate())
|
||||
assert.Nil(t, TLSSpec{AltNames: []string{"foo"}}.Validate())
|
||||
assert.Nil(t, TLSSpec{AltNames: []string{"email@example.com", "127.0.0.1"}}.Validate())
|
||||
|
||||
// Not valid
|
||||
assert.Error(t, TLSSpec{CASecretName: "Foo"}.Validate())
|
||||
assert.Error(t, TLSSpec{AltNames: []string{"@@"}}.Validate())
|
||||
}
|
||||
|
||||
func TestTLSSpecIsSecure(t *testing.T) {
|
||||
assert.False(t, TLSSpec{CASecretName: ""}.IsSecure())
|
||||
assert.True(t, TLSSpec{CASecretName: "foo"}.IsSecure())
|
||||
}
|
||||
|
||||
func TestTLSSpecSetDefaults(t *testing.T) {
|
||||
def := func(spec TLSSpec) TLSSpec {
|
||||
spec.SetDefaults("")
|
||||
return spec
|
||||
}
|
||||
|
||||
assert.Equal(t, "", def(TLSSpec{}).CASecretName)
|
||||
assert.Equal(t, "foo", def(TLSSpec{CASecretName: "foo"}).CASecretName)
|
||||
assert.Len(t, def(TLSSpec{}).AltNames, 0)
|
||||
assert.Len(t, def(TLSSpec{AltNames: []string{"foo.local"}}).AltNames, 1)
|
||||
assert.Equal(t, defaultTLSTTL, def(TLSSpec{}).TTL)
|
||||
assert.Equal(t, time.Hour, def(TLSSpec{TTL: time.Hour}).TTL)
|
||||
}
|
|
@ -154,8 +154,8 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
|||
*out = *in
|
||||
out.RocksDB = in.RocksDB
|
||||
out.Authentication = in.Authentication
|
||||
out.SSL = in.SSL
|
||||
out.Sync = in.Sync
|
||||
in.TLS.DeepCopyInto(&out.TLS)
|
||||
in.Sync.DeepCopyInto(&out.Sync)
|
||||
in.Single.DeepCopyInto(&out.Single)
|
||||
in.Agents.DeepCopyInto(&out.Agents)
|
||||
in.DBServers.DeepCopyInto(&out.DBServers)
|
||||
|
@ -336,22 +336,6 @@ func (in *RocksDBSpec) DeepCopy() *RocksDBSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SSLSpec) DeepCopyInto(out *SSLSpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSLSpec.
|
||||
func (in *SSLSpec) DeepCopy() *SSLSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SSLSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServerGroupSpec) DeepCopyInto(out *ServerGroupSpec) {
|
||||
*out = *in
|
||||
|
@ -378,6 +362,7 @@ func (in *ServerGroupSpec) DeepCopy() *ServerGroupSpec {
|
|||
func (in *SyncSpec) DeepCopyInto(out *SyncSpec) {
|
||||
*out = *in
|
||||
out.Authentication = in.Authentication
|
||||
in.TLS.DeepCopyInto(&out.TLS)
|
||||
out.Monitoring = in.Monitoring
|
||||
return
|
||||
}
|
||||
|
@ -391,3 +376,24 @@ func (in *SyncSpec) DeepCopy() *SyncSpec {
|
|||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLSSpec) DeepCopyInto(out *TLSSpec) {
|
||||
*out = *in
|
||||
if in.AltNames != nil {
|
||||
in, out := &in.AltNames, &out.AltNames
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSpec.
|
||||
func (in *TLSSpec) DeepCopy() *TLSSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TLSSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -65,6 +65,9 @@ func createArangodArgs(apiObject metav1.Object, deplSpec api.DeploymentSpec, gro
|
|||
}*/
|
||||
//scheme := NewURLSchemes(bsCfg.SslKeyFile != "").Arangod
|
||||
scheme := "tcp"
|
||||
if deplSpec.IsSecure() {
|
||||
scheme = "ssl"
|
||||
}
|
||||
options = append(options,
|
||||
optionPair{"--server.endpoint", fmt.Sprintf("%s://%s:%d", scheme, listenAddr, k8sutil.ArangoPort)},
|
||||
)
|
||||
|
@ -93,23 +96,24 @@ func createArangodArgs(apiObject metav1.Object, deplSpec api.DeploymentSpec, gro
|
|||
optionPair{"--log.level", "INFO"},
|
||||
)
|
||||
|
||||
// SSL
|
||||
/*if bsCfg.SslKeyFile != "" {
|
||||
sslSection := &configSection{
|
||||
Name: "ssl",
|
||||
Settings: map[string]string{
|
||||
"keyfile": bsCfg.SslKeyFile,
|
||||
},
|
||||
}
|
||||
if bsCfg.SslCAFile != "" {
|
||||
sslSection.Settings["cafile"] = bsCfg.SslCAFile
|
||||
}
|
||||
config = append(config, sslSection)
|
||||
}*/
|
||||
// TLS
|
||||
if deplSpec.IsSecure() {
|
||||
keyPath := filepath.Join(k8sutil.TLSKeyfileVolumeMountDir, constants.SecretTLSKeyfile)
|
||||
options = append(options,
|
||||
optionPair{"--ssl.keyfile", keyPath},
|
||||
optionPair{"--ssl.ecdh-curve", ""}, // This way arangod accepts curves other than P256 as well.
|
||||
)
|
||||
/*if bsCfg.SslKeyFile != "" {
|
||||
if bsCfg.SslCAFile != "" {
|
||||
sslSection.Settings["cafile"] = bsCfg.SslCAFile
|
||||
}
|
||||
config = append(config, sslSection)
|
||||
}*/
|
||||
}
|
||||
|
||||
// RocksDB
|
||||
if deplSpec.RocksDB.IsEncrypted() {
|
||||
keyPath := filepath.Join(k8sutil.RocksDBEncryptionVolumeMountDir, "key")
|
||||
keyPath := filepath.Join(k8sutil.RocksDBEncryptionVolumeMountDir, constants.SecretEncryptionKey)
|
||||
options = append(options,
|
||||
optionPair{"--rocksdb.encryption-keyfile", keyPath},
|
||||
)
|
||||
|
@ -298,6 +302,7 @@ func (d *Deployment) createReadinessProbe(apiObject *api.ArangoDeployment, group
|
|||
// ensurePods creates all Pods listed in member status
|
||||
func (d *Deployment) ensurePods(apiObject *api.ArangoDeployment) error {
|
||||
kubecli := d.deps.KubeCli
|
||||
log := d.deps.Log
|
||||
ns := apiObject.GetNamespace()
|
||||
|
||||
if err := apiObject.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error {
|
||||
|
@ -318,6 +323,18 @@ func (d *Deployment) ensurePods(apiObject *api.ArangoDeployment) error {
|
|||
if err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
tlsKeyfileSecretName := ""
|
||||
if apiObject.Spec.IsSecure() {
|
||||
tlsKeyfileSecretName = k8sutil.CreateTLSKeyfileSecretName(apiObject.GetName(), role, m.ID)
|
||||
serverNames := []string{
|
||||
k8sutil.CreateDatabaseClientServiceDNSName(apiObject),
|
||||
k8sutil.CreatePodDNSName(apiObject, role, m.ID),
|
||||
}
|
||||
owner := apiObject.AsOwner()
|
||||
if err := createServerCertificate(log, kubecli.CoreV1(), serverNames, apiObject.Spec.TLS, tlsKeyfileSecretName, ns, &owner); err != nil && !k8sutil.IsAlreadyExists(err) {
|
||||
return maskAny(errors.Wrapf(err, "Failed to create TLS keyfile secret"))
|
||||
}
|
||||
}
|
||||
rocksdbEncryptionSecretName := ""
|
||||
if apiObject.Spec.RocksDB.IsEncrypted() {
|
||||
rocksdbEncryptionSecretName = apiObject.Spec.RocksDB.Encryption.KeySecretName
|
||||
|
@ -331,7 +348,7 @@ func (d *Deployment) ensurePods(apiObject *api.ArangoDeployment) error {
|
|||
SecretKey: constants.SecretKeyJWT,
|
||||
}
|
||||
}
|
||||
if err := k8sutil.CreateArangodPod(kubecli, apiObject.Spec.IsDevelopment(), apiObject, role, m.ID, m.PersistentVolumeClaimName, apiObject.Spec.Image, apiObject.Spec.ImagePullPolicy, args, env, livenessProbe, readinessProbe, rocksdbEncryptionSecretName); err != nil {
|
||||
if err := k8sutil.CreateArangodPod(kubecli, apiObject.Spec.IsDevelopment(), apiObject, role, m.ID, m.PersistentVolumeClaimName, apiObject.Spec.Image, apiObject.Spec.ImagePullPolicy, args, env, livenessProbe, readinessProbe, tlsKeyfileSecretName, rocksdbEncryptionSecretName); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
} else if group.IsArangosync() {
|
||||
|
|
|
@ -74,6 +74,52 @@ func TestCreateArangodArgsAgent(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
// Default+TLS deployment
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: api.DeploymentSpec{
|
||||
Mode: api.DeploymentModeCluster,
|
||||
TLS: api.TLSSpec{
|
||||
CASecretName: "test-ca",
|
||||
},
|
||||
},
|
||||
}
|
||||
apiObject.Spec.SetDefaults("test")
|
||||
agents := api.MemberStatusList{
|
||||
api.MemberStatus{ID: "a1"},
|
||||
api.MemberStatus{ID: "a2"},
|
||||
api.MemberStatus{ID: "a3"},
|
||||
}
|
||||
cmdline := createArangodArgs(apiObject, apiObject.Spec, api.ServerGroupAgents, apiObject.Spec.Agents, agents, "a1")
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"--agency.activate=true",
|
||||
"--agency.endpoint=ssl://name-agent-a2.name-int.ns.svc:8529",
|
||||
"--agency.endpoint=ssl://name-agent-a3.name-int.ns.svc:8529",
|
||||
"--agency.my-address=ssl://name-agent-a1.name-int.ns.svc:8529",
|
||||
"--agency.size=3",
|
||||
"--agency.supervision=true",
|
||||
"--cluster.my-id=a1",
|
||||
"--database.directory=/data",
|
||||
"--foxx.queues=false",
|
||||
"--log.level=INFO",
|
||||
"--log.output=+",
|
||||
"--server.authentication=true",
|
||||
"--server.endpoint=ssl://[::]:8529",
|
||||
"--server.jwt-secret=$(ARANGOD_JWT_SECRET)",
|
||||
"--server.statistics=false",
|
||||
"--server.storage-engine=rocksdb",
|
||||
"--ssl.ecdh-curve=",
|
||||
"--ssl.keyfile=/secrets/tls/tls.keyfile",
|
||||
},
|
||||
cmdline,
|
||||
)
|
||||
}
|
||||
|
||||
// No authentication, mmfiles
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
|
|
|
@ -73,6 +73,51 @@ func TestCreateArangodArgsCoordinator(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
// Default+TLS deployment
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: api.DeploymentSpec{
|
||||
Mode: api.DeploymentModeCluster,
|
||||
TLS: api.TLSSpec{
|
||||
CASecretName: "test-ca",
|
||||
},
|
||||
},
|
||||
}
|
||||
apiObject.Spec.SetDefaults("test")
|
||||
agents := api.MemberStatusList{
|
||||
api.MemberStatus{ID: "a1"},
|
||||
api.MemberStatus{ID: "a2"},
|
||||
api.MemberStatus{ID: "a3"},
|
||||
}
|
||||
cmdline := createArangodArgs(apiObject, apiObject.Spec, api.ServerGroupCoordinators, apiObject.Spec.Coordinators, agents, "id1")
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"--cluster.agency-endpoint=ssl://name-agent-a1.name-int.ns.svc:8529",
|
||||
"--cluster.agency-endpoint=ssl://name-agent-a2.name-int.ns.svc:8529",
|
||||
"--cluster.agency-endpoint=ssl://name-agent-a3.name-int.ns.svc:8529",
|
||||
"--cluster.my-address=ssl://name-coordinator-id1.name-int.ns.svc:8529",
|
||||
"--cluster.my-id=id1",
|
||||
"--cluster.my-role=COORDINATOR",
|
||||
"--database.directory=/data",
|
||||
"--foxx.queues=true",
|
||||
"--log.level=INFO",
|
||||
"--log.output=+",
|
||||
"--server.authentication=true",
|
||||
"--server.endpoint=ssl://[::]:8529",
|
||||
"--server.jwt-secret=$(ARANGOD_JWT_SECRET)",
|
||||
"--server.statistics=true",
|
||||
"--server.storage-engine=rocksdb",
|
||||
"--ssl.ecdh-curve=",
|
||||
"--ssl.keyfile=/secrets/tls/tls.keyfile",
|
||||
},
|
||||
cmdline,
|
||||
)
|
||||
}
|
||||
|
||||
// No authentication
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
|
|
|
@ -73,6 +73,51 @@ func TestCreateArangodArgsDBServer(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
// Default+TLS deployment
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: api.DeploymentSpec{
|
||||
Mode: api.DeploymentModeCluster,
|
||||
TLS: api.TLSSpec{
|
||||
CASecretName: "test-ca",
|
||||
},
|
||||
},
|
||||
}
|
||||
apiObject.Spec.SetDefaults("test")
|
||||
agents := api.MemberStatusList{
|
||||
api.MemberStatus{ID: "a1"},
|
||||
api.MemberStatus{ID: "a2"},
|
||||
api.MemberStatus{ID: "a3"},
|
||||
}
|
||||
cmdline := createArangodArgs(apiObject, apiObject.Spec, api.ServerGroupDBServers, apiObject.Spec.DBServers, agents, "id1")
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"--cluster.agency-endpoint=ssl://name-agent-a1.name-int.ns.svc:8529",
|
||||
"--cluster.agency-endpoint=ssl://name-agent-a2.name-int.ns.svc:8529",
|
||||
"--cluster.agency-endpoint=ssl://name-agent-a3.name-int.ns.svc:8529",
|
||||
"--cluster.my-address=ssl://name-dbserver-id1.name-int.ns.svc:8529",
|
||||
"--cluster.my-id=id1",
|
||||
"--cluster.my-role=PRIMARY",
|
||||
"--database.directory=/data",
|
||||
"--foxx.queues=false",
|
||||
"--log.level=INFO",
|
||||
"--log.output=+",
|
||||
"--server.authentication=true",
|
||||
"--server.endpoint=ssl://[::]:8529",
|
||||
"--server.jwt-secret=$(ARANGOD_JWT_SECRET)",
|
||||
"--server.statistics=true",
|
||||
"--server.storage-engine=rocksdb",
|
||||
"--ssl.ecdh-curve=",
|
||||
"--ssl.keyfile=/secrets/tls/tls.keyfile",
|
||||
},
|
||||
cmdline,
|
||||
)
|
||||
}
|
||||
|
||||
// No authentication
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
|
|
|
@ -58,6 +58,36 @@ func TestCreateArangodArgsSingle(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
// Default+TLS deployment
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
Spec: api.DeploymentSpec{
|
||||
Mode: api.DeploymentModeSingle,
|
||||
TLS: api.TLSSpec{
|
||||
CASecretName: "test-ca",
|
||||
},
|
||||
},
|
||||
}
|
||||
apiObject.Spec.SetDefaults("test")
|
||||
cmdline := createArangodArgs(apiObject, apiObject.Spec, api.ServerGroupSingle, apiObject.Spec.Single, nil, "id1")
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"--database.directory=/data",
|
||||
"--foxx.queues=true",
|
||||
"--log.level=INFO",
|
||||
"--log.output=+",
|
||||
"--server.authentication=true",
|
||||
"--server.endpoint=ssl://[::]:8529",
|
||||
"--server.jwt-secret=$(ARANGOD_JWT_SECRET)",
|
||||
"--server.statistics=true",
|
||||
"--server.storage-engine=rocksdb",
|
||||
"--ssl.ecdh-curve=",
|
||||
"--ssl.keyfile=/secrets/tls/tls.keyfile",
|
||||
},
|
||||
cmdline,
|
||||
)
|
||||
}
|
||||
|
||||
// Default deployment with mmfiles
|
||||
{
|
||||
apiObject := &api.ArangoDeployment{
|
||||
|
|
|
@ -40,6 +40,16 @@ func (d *Deployment) createSecrets(apiObject *api.ArangoDeployment) error {
|
|||
return maskAny(err)
|
||||
}
|
||||
}
|
||||
if apiObject.Spec.IsSecure() {
|
||||
if err := d.ensureCACertificateSecret(apiObject.Spec.TLS); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
}
|
||||
if apiObject.Spec.Sync.Enabled {
|
||||
if err := d.ensureCACertificateSecret(apiObject.Spec.Sync.TLS); err != nil {
|
||||
return maskAny(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -72,6 +82,30 @@ func (d *Deployment) ensureJWTSecret(secretName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ensureCACertificateSecret checks if a secret with given name exists in the namespace
|
||||
// of the deployment. If not, it will add such a secret with a generated CA certificate.
|
||||
// JWT token.
|
||||
func (d *Deployment) ensureCACertificateSecret(spec api.TLSSpec) error {
|
||||
kubecli := d.deps.KubeCli
|
||||
ns := d.apiObject.GetNamespace()
|
||||
if _, err := kubecli.CoreV1().Secrets(ns).Get(spec.CASecretName, metav1.GetOptions{}); k8sutil.IsNotFound(err) {
|
||||
// Secret not found, create it
|
||||
owner := d.apiObject.AsOwner()
|
||||
deploymentName := d.apiObject.GetName()
|
||||
if err := createCACertificate(d.deps.Log, kubecli.CoreV1(), spec, deploymentName, ns, &owner); k8sutil.IsAlreadyExists(err) {
|
||||
// Secret added while we tried it also
|
||||
return nil
|
||||
} else if err != nil {
|
||||
// Failed to create secret
|
||||
return maskAny(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
// Failed to get secret for other reasons
|
||||
return maskAny(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getJWTSecret loads the JWT secret from a Secret configured in apiObject.Spec.Authentication.JWTSecretName.
|
||||
func (d *Deployment) getJWTSecret(apiObject *api.ArangoDeployment) (string, error) {
|
||||
if !apiObject.Spec.IsAuthenticated() {
|
||||
|
|
129
pkg/deployment/tls.go
Normal file
129
pkg/deployment/tls.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
certificates "github.com/arangodb-helper/go-certificates"
|
||||
"github.com/rs/zerolog"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
||||
api "github.com/arangodb/k8s-operator/pkg/apis/arangodb/v1alpha"
|
||||
"github.com/arangodb/k8s-operator/pkg/util/k8sutil"
|
||||
)
|
||||
|
||||
const (
|
||||
caTTL = time.Hour * 24 * 365 * 10 // 10 year
|
||||
tlsECDSACurve = "P256" // This curve is the default that ArangoDB accepts and plenty strong
|
||||
)
|
||||
|
||||
// createCACertificate creates a CA certificate and stores it in a secret with name
|
||||
// specified in the given spec.
|
||||
func createCACertificate(log zerolog.Logger, cli v1.CoreV1Interface, spec api.TLSSpec, deploymentName, namespace string, ownerRef *metav1.OwnerReference) error {
|
||||
log = log.With().Str("secret", spec.CASecretName).Logger()
|
||||
dnsNames, ipAddresses, emailAddress, err := spec.GetAltNames()
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to get alternate names")
|
||||
return maskAny(err)
|
||||
}
|
||||
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: fmt.Sprintf("%s Root Certificate", deploymentName),
|
||||
Hosts: append(dnsNames, ipAddresses...),
|
||||
EmailAddresses: emailAddress,
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: caTTL,
|
||||
IsCA: true,
|
||||
ECDSACurve: tlsECDSACurve,
|
||||
}
|
||||
cert, priv, err := certificates.CreateCertificate(options, nil)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to create CA certificate")
|
||||
return maskAny(err)
|
||||
}
|
||||
if err := k8sutil.CreateCASecret(cli, spec.CASecretName, namespace, cert, priv, ownerRef); err != nil {
|
||||
if k8sutil.IsAlreadyExists(err) {
|
||||
log.Debug().Msg("CA Secret already exists")
|
||||
} else {
|
||||
log.Debug().Err(err).Msg("Failed to create CA Secret")
|
||||
}
|
||||
return maskAny(err)
|
||||
}
|
||||
log.Debug().Msg("Created CA Secret")
|
||||
return nil
|
||||
}
|
||||
|
||||
// createServerCertificate creates a TLS certificate for a specific server and stores
|
||||
// it in a secret with the given name.
|
||||
func createServerCertificate(log zerolog.Logger, cli v1.CoreV1Interface, serverNames []string, spec api.TLSSpec, secretName, namespace string, ownerRef *metav1.OwnerReference) error {
|
||||
log = log.With().Str("secret", secretName).Logger()
|
||||
// Load alt names
|
||||
dnsNames, ipAddresses, emailAddress, err := spec.GetAltNames()
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to get alternate names")
|
||||
return maskAny(err)
|
||||
}
|
||||
|
||||
// Load CA certificate
|
||||
caCert, caKey, err := k8sutil.GetCASecret(cli, spec.CASecretName, namespace)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to load CA certificate")
|
||||
return maskAny(err)
|
||||
}
|
||||
ca, err := certificates.LoadCAFromPEM(caCert, caKey)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to decode CA certificate")
|
||||
return maskAny(err)
|
||||
}
|
||||
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: serverNames[0],
|
||||
Hosts: append(append(serverNames, dnsNames...), ipAddresses...),
|
||||
EmailAddresses: emailAddress,
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: spec.TTL,
|
||||
IsCA: false,
|
||||
ECDSACurve: tlsECDSACurve,
|
||||
}
|
||||
cert, priv, err := certificates.CreateCertificate(options, &ca)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Failed to create server certificate")
|
||||
return maskAny(err)
|
||||
}
|
||||
keyfile := strings.TrimSpace(cert) + "\n" +
|
||||
strings.TrimSpace(priv)
|
||||
if err := k8sutil.CreateTLSKeyfileSecret(cli, secretName, namespace, keyfile, ownerRef); err != nil {
|
||||
if k8sutil.IsAlreadyExists(err) {
|
||||
log.Debug().Msg("Server Secret already exists")
|
||||
} else {
|
||||
log.Debug().Err(err).Msg("Failed to create server Secret")
|
||||
}
|
||||
return maskAny(err)
|
||||
}
|
||||
log.Debug().Msg("Created server Secret")
|
||||
return nil
|
||||
}
|
|
@ -32,4 +32,9 @@ const (
|
|||
|
||||
SecretEncryptionKey = "key" // Key in a Secret.Data used to store an 32-byte encryption key
|
||||
SecretKeyJWT = "token" // Key inside a Secret used to hold a JW token
|
||||
|
||||
SecretCACertificate = "ca.crt" // Key in Secret.data used to store a PEM encoded CA certificate (public key)
|
||||
SecretCAKey = "ca.key" // Key in Secret.data used to store a PEM encoded CA private key
|
||||
|
||||
SecretTLSKeyfile = "tls.keyfile" // Key in Secret.data used to store a PEM encoded TLS certificate in the format used by ArangoDB (`--ssl.keyfile`)
|
||||
)
|
||||
|
|
|
@ -30,9 +30,11 @@ import (
|
|||
|
||||
const (
|
||||
arangodVolumeName = "arangod-data"
|
||||
tlsKeyfileVolumeName = "tls-keyfile"
|
||||
rocksdbEncryptionVolumeName = "rocksdb-encryption"
|
||||
ArangodVolumeMountDir = "/data"
|
||||
RocksDBEncryptionVolumeMountDir = "/secrets/rocksdb/encryption"
|
||||
TLSKeyfileVolumeMountDir = "/secrets/tls"
|
||||
)
|
||||
|
||||
// EnvValue is a helper structure for environment variable sources.
|
||||
|
@ -98,6 +100,12 @@ func CreatePodName(deploymentName, role, id string) string {
|
|||
return deploymentName + "-" + role + "-" + id
|
||||
}
|
||||
|
||||
// CreateTLSKeyfileSecretName returns the name of the Secret that holds the TLS keyfile for a member with
|
||||
// a given id in a deployment with a given name.
|
||||
func CreateTLSKeyfileSecretName(deploymentName, role, id string) string {
|
||||
return CreatePodName(deploymentName, role, id) + "-tls-keyfile"
|
||||
}
|
||||
|
||||
// arangodVolumeMounts creates a volume mount structure for arangod.
|
||||
func arangodVolumeMounts() []v1.VolumeMount {
|
||||
return []v1.VolumeMount{
|
||||
|
@ -105,7 +113,17 @@ func arangodVolumeMounts() []v1.VolumeMount {
|
|||
}
|
||||
}
|
||||
|
||||
// arangodVolumeMounts creates a volume mount structure for arangod.
|
||||
// tlsKeyfileVolumeMounts creates a volume mount structure for a TLS keyfile.
|
||||
func tlsKeyfileVolumeMounts() []v1.VolumeMount {
|
||||
return []v1.VolumeMount{
|
||||
{
|
||||
Name: tlsKeyfileVolumeName,
|
||||
MountPath: TLSKeyfileVolumeMountDir,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// rocksdbEncryptionVolumeMounts creates a volume mount structure for a RocksDB encryption key.
|
||||
func rocksdbEncryptionVolumeMounts() []v1.VolumeMount {
|
||||
return []v1.VolumeMount{
|
||||
{
|
||||
|
@ -193,12 +211,15 @@ func CreateArangodPod(kubecli kubernetes.Interface, developmentMode bool, deploy
|
|||
role, id, pvcName, image string, imagePullPolicy v1.PullPolicy,
|
||||
args []string, env map[string]EnvValue,
|
||||
livenessProbe *HTTPProbeConfig, readinessProbe *HTTPProbeConfig,
|
||||
rocksdbEncryptionSecretName string) error {
|
||||
tlsKeyfileSecretName, rocksdbEncryptionSecretName string) error {
|
||||
// Prepare basic pod
|
||||
p := newPod(deployment.GetName(), deployment.GetNamespace(), role, id)
|
||||
|
||||
// Add arangod container
|
||||
c := arangodContainer(p.GetName(), image, imagePullPolicy, args, env, livenessProbe, readinessProbe)
|
||||
if tlsKeyfileSecretName != "" {
|
||||
c.VolumeMounts = append(c.VolumeMounts, tlsKeyfileVolumeMounts()...)
|
||||
}
|
||||
if rocksdbEncryptionSecretName != "" {
|
||||
c.VolumeMounts = append(c.VolumeMounts, rocksdbEncryptionVolumeMounts()...)
|
||||
}
|
||||
|
@ -227,6 +248,19 @@ func CreateArangodPod(kubecli kubernetes.Interface, developmentMode bool, deploy
|
|||
p.Spec.Volumes = append(p.Spec.Volumes, vol)
|
||||
}
|
||||
|
||||
// TLS keyfile secret mount (if any)
|
||||
if tlsKeyfileSecretName != "" {
|
||||
vol := v1.Volume{
|
||||
Name: tlsKeyfileVolumeName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: tlsKeyfileSecretName,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.Spec.Volumes = append(p.Spec.Volumes, vol)
|
||||
}
|
||||
|
||||
// RocksDB encryption secret mount (if any)
|
||||
if rocksdbEncryptionSecretName != "" {
|
||||
vol := v1.Volume{
|
||||
|
|
|
@ -71,6 +71,70 @@ func CreateEncryptionKeySecret(cli corev1.CoreV1Interface, secretName, namespace
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetCASecret loads a secret with given name in the given namespace
|
||||
// and extracts the `ca.crt` & `ca.key` field.
|
||||
// If the secret does not exists or one of the fields is missing,
|
||||
// an error is returned.
|
||||
// Returns: certificate, private-key, error
|
||||
func GetCASecret(cli corev1.CoreV1Interface, secretName, namespace string) (string, string, error) {
|
||||
s, err := cli.Secrets(namespace).Get(secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", "", maskAny(err)
|
||||
}
|
||||
// Load `ca.crt` field
|
||||
cert, found := s.Data[constants.SecretCACertificate]
|
||||
if !found {
|
||||
return "", "", maskAny(fmt.Errorf("No '%s' found in secret '%s'", constants.SecretCACertificate, secretName))
|
||||
}
|
||||
priv, found := s.Data[constants.SecretCAKey]
|
||||
if !found {
|
||||
return "", "", maskAny(fmt.Errorf("No '%s' found in secret '%s'", constants.SecretCAKey, secretName))
|
||||
}
|
||||
return string(cert), string(priv), nil
|
||||
}
|
||||
|
||||
// CreateCASecret creates a secret used to store a PEM encoded CA certificate & private key.
|
||||
func CreateCASecret(cli corev1.CoreV1Interface, secretName, namespace string, certificate, key string, ownerRef *metav1.OwnerReference) error {
|
||||
// Create secret
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
constants.SecretCACertificate: []byte(certificate),
|
||||
constants.SecretCAKey: []byte(key),
|
||||
},
|
||||
}
|
||||
// Attach secret to owner
|
||||
addOwnerRefToObject(secret, ownerRef)
|
||||
if _, err := cli.Secrets(namespace).Create(secret); err != nil {
|
||||
// Failed to create secret
|
||||
return maskAny(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTLSKeyfileSecret creates a secret used to store a PEM encoded keyfile
|
||||
// in the format ArangoDB accepts it for its `--ssl.keyfile` option.
|
||||
func CreateTLSKeyfileSecret(cli corev1.CoreV1Interface, secretName, namespace string, keyfile string, ownerRef *metav1.OwnerReference) error {
|
||||
// Create secret
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
constants.SecretTLSKeyfile: []byte(keyfile),
|
||||
},
|
||||
}
|
||||
// Attach secret to owner
|
||||
addOwnerRefToObject(secret, ownerRef)
|
||||
if _, err := cli.Secrets(namespace).Create(secret); err != nil {
|
||||
// Failed to create secret
|
||||
return maskAny(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetJWTSecret loads the JWT secret from a Secret with given name.
|
||||
func GetJWTSecret(cli corev1.CoreV1Interface, secretName, namespace string) (string, error) {
|
||||
s, err := cli.Secrets(namespace).Get(secretName, metav1.GetOptions{})
|
||||
|
|
42
pkg/util/validation/dns_name.go
Normal file
42
pkg/util/validation/dns_name.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
dnsNameRegexp = regexp.MustCompile(`^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`)
|
||||
)
|
||||
|
||||
// IsValidDNSName returns true when the given input is a valid DNS name
|
||||
func IsValidDNSName(input string) bool {
|
||||
// IsDNSName will validate the given string as a DNS name
|
||||
if input == "" || len(strings.Replace(input, ".", "", -1)) > 255 {
|
||||
// constraints already violated
|
||||
return false
|
||||
}
|
||||
return dnsNameRegexp.MatchString(input)
|
||||
}
|
48
pkg/util/validation/dns_name_test.go
Normal file
48
pkg/util/validation/dns_name_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidDNSName(t *testing.T) {
|
||||
valid := []string{
|
||||
"foo.example.com",
|
||||
}
|
||||
invalid := []string{
|
||||
"verylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylongverylong",
|
||||
"@.com",
|
||||
" .arangodb.com",
|
||||
"invalid.arangodb . com",
|
||||
}
|
||||
|
||||
for _, x := range valid {
|
||||
assert.True(t, IsValidDNSName(x), x)
|
||||
}
|
||||
for _, x := range invalid {
|
||||
assert.False(t, IsValidDNSName(x), x)
|
||||
}
|
||||
}
|
34
pkg/util/validation/email_address.go
Normal file
34
pkg/util/validation/email_address.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package validation
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
)
|
||||
|
||||
// IsValidEmailAddress returns true when the given input is a valid email address
|
||||
func IsValidEmailAddress(input string) bool {
|
||||
return emailRegexp.MatchString(input)
|
||||
}
|
46
pkg/util/validation/email_address_test.go
Normal file
46
pkg/util/validation/email_address_test.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Ewout Prangsma
|
||||
//
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidEmailAddress(t *testing.T) {
|
||||
valid := []string{
|
||||
"foo@example.com",
|
||||
}
|
||||
invalid := []string{
|
||||
"ç$?§/az@gmail.com",
|
||||
"foo@example_underscore.com",
|
||||
}
|
||||
|
||||
for _, x := range valid {
|
||||
assert.True(t, IsValidEmailAddress(x), x)
|
||||
}
|
||||
for _, x := range invalid {
|
||||
assert.False(t, IsValidEmailAddress(x), x)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue