Skip to content

Commit

Permalink
fix: Generate TLS Certificates on startup and only keep in memory (ar…
Browse files Browse the repository at this point in the history
…goproj#6540)

Signed-off-by: David Collom <[email protected]>
  • Loading branch information
davidcollom authored Aug 17, 2021
1 parent 5464c4c commit 478d794
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 34 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ git-ask-pass.sh
/workflow-controller
/.scannerwork/
/test-results/
/argo-server.crt
/argo-server.key
/package-lock.json
/pkg/apiclient/_.secondary.swagger.json
/pkg/apiclient/clusterworkflowtemplate/cluster-workflow-template.swagger.json
Expand Down
2 changes: 0 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ WORKDIR /home/argo
COPY hack/ssh_known_hosts /etc/ssh/
COPY hack/nsswitch.conf /etc/
COPY --from=argocli-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=argocli-build --chown=8737 /go/src/github.com/argoproj/argo-workflows/argo-server.crt /home/argo/
COPY --from=argocli-build --chown=8737 /go/src/github.com/argoproj/argo-workflows/argo-server.key /home/argo/
COPY --from=argocli-build /go/src/github.com/argoproj/argo-workflows/dist/argo /bin/

ENTRYPOINT [ "argo" ]
9 changes: 2 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -174,22 +174,17 @@ dist/argo-linux-s390x: GOARGS = GOOS=linux GOARCH=s390x
dist/argo-%.gz: dist/argo-%
gzip --force --keep dist/argo-$*

dist/argo-%: server/static/files.go argo-server.crt argo-server.key $(CLI_PKGS) go.sum
dist/argo-%: server/static/files.go $(CLI_PKGS) go.sum
CGO_ENABLED=0 $(GOARGS) go build -v -ldflags '${LDFLAGS} -extldflags -static' -o $@ ./cmd/argo

dist/argo: server/static/files.go argo-server.crt argo-server.key $(CLI_PKGS) go.sum
dist/argo: server/static/files.go $(CLI_PKGS) go.sum
ifeq ($(shell uname -s),Darwin)
# if local, then build fast: use CGO and dynamic-linking
go build -v -ldflags '${LDFLAGS}' -o $@ ./cmd/argo
else
CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS} -extldflags -static' -o $@ ./cmd/argo
endif

argo-server.crt: argo-server.key

argo-server.key:
openssl req -x509 -newkey rsa:4096 -keyout argo-server.key -out argo-server.crt -days 365 -nodes -subj /CN=localhost/O=ArgoProj

argocli-image:

.PHONY: clis
Expand Down
16 changes: 8 additions & 8 deletions cmd/argo/commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/argoproj/argo-workflows/v3/server/types"
"github.com/argoproj/argo-workflows/v3/util/cmd"
"github.com/argoproj/argo-workflows/v3/util/help"
tlsutils "github.com/argoproj/argo-workflows/v3/util/tls"
)

func NewServerCommand() *cobra.Command {
Expand Down Expand Up @@ -99,18 +100,19 @@ See %s`, help.ArgoServer),

var tlsConfig *tls.Config
if secure {
cer, err := tls.LoadX509KeyPair("argo-server.crt", "argo-server.key")
log.Infof("Generating Self Signed TLS Certificates for Secure Mode")
tlsMinVersion, err := env.GetInt("TLS_MIN_VERSION", tls.VersionTLS12)
if err != nil {
return err
}
tlsMinVersion, err := env.GetInt("TLS_MIN_VERSION", tls.VersionTLS12)
var cer *tls.Certificate
cer, err = tlsutils.GenerateX509KeyPair()
if err != nil {
return err
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cer},
InsecureSkipVerify: true,
MinVersion: uint16(tlsMinVersion),
Certificates: []tls.Certificate{*cer},
MinVersion: uint16(tlsMinVersion),
}
} else {
log.Warn("You are running in insecure mode. Learn how to enable transport layer security: https://argoproj.github.io/argo-workflows/tls/")
Expand Down Expand Up @@ -182,9 +184,7 @@ See %s`, help.ArgoServer),
}
command.Flags().StringVar(&baseHRef, "basehref", defaultBaseHRef, "Value for base href in index.html. Used if the server is running behind reverse proxy under subpath different from /. Defaults to the environment variable BASE_HREF.")
// "-e" for encrypt, like zip
// We default to secure mode if we find certs available, otherwise we default to insecure mode.
_, err := os.Stat("argo-server.crt")
command.Flags().BoolVarP(&secure, "secure", "e", !os.IsNotExist(err), "Whether or not we should listen on TLS.")
command.Flags().BoolVarP(&secure, "secure", "e", true, "Whether or not we should listen on TLS.")
command.Flags().BoolVar(&htst, "hsts", true, "Whether or not we should add a HTTP Secure Transport Security header. This only has effect if secure is enabled.")
command.Flags().StringArrayVar(&authModes, "auth-mode", []string{"client"}, "API server authentication mode. Any 1 or more length permutation of: client,server,sso")
command.Flags().StringVar(&configMap, "configmap", "workflow-controller-configmap", "Name of K8s configmap to retrieve workflow controller configuration")
Expand Down
16 changes: 1 addition & 15 deletions cmd/argo/commands/server_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
package commands

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestDefaultSecureMode(t *testing.T) {
// No certs: We should run insecure
// Secure mode by default
cmd := NewServerCommand()
assert.Equal(t, "false", cmd.Flag("secure").Value.String())

// Clean up and delete tests files
defer func() {
_ = os.Remove("argo-server.crt")
_ = os.Remove("argo-server.key")
}()

_, _ = os.Create("argo-server.crt")
_, _ = os.Create("argo-server.key")

// No certs: We should secure
cmd = NewServerCommand()
assert.Equal(t, "true", cmd.Flag("secure").Value.String())
}
102 changes: 102 additions & 0 deletions util/tls/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package tls

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"os"
"time"
)

func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
log.Fatal(err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}

func generate() ([]byte, crypto.PrivateKey, error) {
hosts := []string{"localhost"}

var err error
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate private key: %s", err)
}

notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate serial number: %s", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"ArgoProj"},
},
NotBefore: notBefore,
NotAfter: notAfter,

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}

certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate: %s", err)
}
return certBytes, privateKey, nil
}

// generatePEM generates a new certificate and key and returns it as PEM encoded bytes
func generatePEM() ([]byte, []byte, error) {
certBytes, privateKey, err := generate()
if err != nil {
return nil, nil, err
}
certpem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
keypem := pem.EncodeToMemory(pemBlockForKey(privateKey))
return certpem, keypem, nil
}

// GenerateX509KeyPair generates a X509 key pair
func GenerateX509KeyPair() (*tls.Certificate, error) {
certpem, keypem, err := generatePEM()
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(certpem, keypem)
if err != nil {
return nil, err
}
return &cert, nil
}
39 changes: 39 additions & 0 deletions util/tls/tls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tls

import (
"crypto/x509"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestGenerate(t *testing.T) {
t.Run("Create certificate with default options", func(t *testing.T) {
certBytes, privKey, err := generate()
assert.NoError(t, err)
assert.NotNil(t, privKey)
cert, err := x509.ParseCertificate(certBytes)
assert.NoError(t, err)
assert.NotNil(t, cert)
assert.Len(t, cert.DNSNames, 1)
assert.Equal(t, "localhost", cert.DNSNames[0])
assert.Empty(t, cert.IPAddresses)
assert.LessOrEqual(t, int64(time.Since(cert.NotBefore)), int64(10*time.Second))
})
}

func TestGeneratePEM(t *testing.T) {
t.Run("Create PEM from certficate options", func(t *testing.T) {
cert, key, err := generatePEM()
assert.NoError(t, err)
assert.NotNil(t, cert)
assert.NotNil(t, key)
})

t.Run("Create X509KeyPair", func(t *testing.T) {
cert, err := GenerateX509KeyPair()
assert.NoError(t, err)
assert.NotNil(t, cert)
})
}

0 comments on commit 478d794

Please sign in to comment.