Skip to content

Commit

Permalink
Add SPIFFE Federation Examples (#1162)
Browse files Browse the repository at this point in the history
* remove static federation config

* add bundle enpoints back

* config update

* wip

* wip

* wip

* wip

* update image urls

* manifest changes

* images

* key rotation

* lastworking

* update.

* add kubectl

* add statefulset.

* more

* wip

* chmod

* a

* sed

se

* export secrets

* update server code

* update deployment

* update client and server

* add error logging

* minor updates

* add error details

* more logs

* change

* printing error

* fix encryption

* update client

* client check

* algo update

* cleanup

* last changes.

* edge store

* updated logic.

* recent changes

* a

* add env variable for custom spiffeid prefix.

* add env var.

* asdfasd

* update dpeloyment.

* changes

* minor

* up

* a

* bugfix

* changes
  • Loading branch information
v0lkan authored Oct 7, 2024
1 parent 16f26e1 commit 0bffb89
Show file tree
Hide file tree
Showing 32 changed files with 4,081 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ COPY go.sum .
RUN go mod download

COPY server.go .
COPY crypto.go .
COPY network.go .
COPY entity.go .
COPY io.go .

RUN CGO_ENABLED=0 GOOS=linux go build -o server server.go
RUN CGO_ENABLED=0 GOOS=linux go build -o server server.go crypto.go network.go io.go entity.go

FROM ubuntu:22.04 AS server

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package main

import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
)

func signData(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) {
hash := sha256.Sum256(data)
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:])
if err != nil {
return nil, fmt.Errorf("error signing data: %v", err)
}

return signature, nil
}

func parsePublicKey(publicKeyPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the public key")
}

pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}

rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("not an RSA public key")
}

return rsaPub, nil
}

func generateAESKey() ([]byte, error) {
key := make([]byte, 32) // AES-256
_, err := rand.Read(key)
return key, err
}

func encryptAES(plaintext []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}

return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

func generateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
return privateKey, &privateKey.PublicKey, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

type Endpoint struct {
Name string `json:"name"`
BundleEndpointURL string `json:"bundleEndpointUrl"`
TrustDomain string `json:"trustDomain"`
EndpointSPIFFEID string `json:"endpointSPIFFEID"`
}

type Endpoints struct {
Endpoints map[string]Endpoint `json:""`
FederateWith []string `json:"federateWith"`
}

type Secret struct {
Name string `json:"name"`
Value []string `json:"value"`
Created string `json:"created"`
Updated string `json:"updated"`
NotBefore string `json:"notBefore"`
ExpiresAfter string `json:"expiresAfter"`
}

type EncryptedResponse struct {
EncryptedAESKey string `json:"encryptedAESKey"`
EncryptedData string `json:"encryptedData"`
Signature string `json:"signature"`
}

type Secrets struct {
Secrets []Secret `json:"secrets"`
Algorithm string `json:"algorithm"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"encoding/json"
"os"
)

func loadEndpoints(filename string) Endpoints {
data, err := os.ReadFile(filename)
if err != nil {
panic("Error reading endpoints file: " + err.Error())
}

var endpoints Endpoints
err = json.Unmarshal(data, &endpoints)
if err != nil {
panic("Error parsing endpoints JSON: " + err.Error())
}

return endpoints
}

func loadSecrets(filename string) Secrets {
data, err := os.ReadFile(filename)
if err != nil {
panic("Error reading secrets file: " + err.Error())
}

var secrets Secrets
err = json.Unmarshal(data, &secrets)
if err != nil {
panic("Error parsing secrets JSON: " + err.Error())
}

return secrets
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
"strings"
)

func handleRequest(w http.ResponseWriter, r *http.Request, secrets Secrets) {
// Extract SPIFFE ID from the client certificate
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
http.Error(w, "No client certificate provided", http.StatusUnauthorized)
return
}

spiffeID := r.TLS.PeerCertificates[0].URIs[0].String()
fmt.Printf("Received request from SPIFFE ID: %s\n", spiffeID)

// Extract trust domain from SPIFFE ID
parts := strings.Split(spiffeID, "/")
if len(parts) < 3 {
fmt.Println("Invalid SPIFFE ID")
http.Error(w, "Invalid SPIFFE ID", http.StatusBadRequest)
return
}
trustDomain := parts[2]

// Read the client's public key from the request body
body, err := io.ReadAll(r.Body)
if err != nil {
fmt.Println("error in request body")
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
clientPublicKey, err := parsePublicKey(string(body))
if err != nil {
fmt.Println("error in parsing public key")
http.Error(w, "Invalid client public key", http.StatusBadRequest)
return
}

// Generate a new keypair for this response
privateKey, publicKey, err := generateKeyPair()
if err != nil {
http.Error(w, "Error generating keypair", http.StatusInternalServerError)
return
}

// Find the corresponding secret
secretName := fmt.Sprintf("vsecm-relay:%s", trustDomain)
var secretValue []string
for _, secret := range secrets.Secrets {
if secret.Name == secretName {
secretValue = secret.Value
break
}
}

if secretValue == nil {
http.Error(w, "No secret found for the given SPIFFE ID", http.StatusNotFound)
return
}

// Generate AES key
aesKey, err := generateAESKey()
if err != nil {
http.Error(w, "Error generating AES key", http.StatusInternalServerError)
return
}

// Encrypt the AES key with the client's public key
encryptedAESKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, clientPublicKey, aesKey, nil)
if err != nil {
http.Error(w, "Error encrypting AES key", http.StatusInternalServerError)
return
}

// Encrypt the secret with AES
plaintext, err := json.Marshal(secretValue)
if err != nil {
http.Error(w, "Error marshaling secret", http.StatusInternalServerError)
return
}
encryptedData, err := encryptAES(plaintext, aesKey)
if err != nil {
http.Error(w, "Error encrypting data", http.StatusInternalServerError)
return
}

// Sign the encrypted data
signature, err := signData(encryptedData, privateKey)
if err != nil {
http.Error(w, "Error signing data", http.StatusInternalServerError)
return
}

// Prepare the response
response := EncryptedResponse{
EncryptedAESKey: base64.StdEncoding.EncodeToString(encryptedAESKey),
EncryptedData: base64.StdEncoding.EncodeToString(encryptedData),
Signature: base64.StdEncoding.EncodeToString(signature),
}

publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
http.Error(w, "Error encoding public key", http.StatusInternalServerError)
return
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})

// Add the public key to the response headers
w.Header().Set("X-Public-Key", base64.StdEncoding.EncodeToString(publicKeyPEM))

// Send the response
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(response)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
/*
| Protect your secrets, protect your sensitive data.
: Explore VMware Secrets Manager docs at https://vsecm.com/
</
<>/ keep your secrets... secret
>/
<>/' Copyright 2023-present VMware Secrets Manager contributors.
>/' SPDX-License-Identifier: BSD-2-Clause
*/

package main

import (
Expand All @@ -18,31 +8,34 @@ import (
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/spiffe/go-spiffe/v2/workloadapi"
"net/http"
"sync"
)

func main() {
fmt.Println("In main...")
fmt.Println("Starting mTLS Secret Relay Server...")

// Load endpoints and secrets
// endpoints = loadEndpoints("/vsecm-relay/data/endpoints.json")
secrets := loadSecrets("/vsecm-relay/data/secrets.json")

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

fmt.Println("Before querying the workload api")

source, err := workloadapi.NewX509Source(
ctx,
workloadapi.WithClientOptions(
workloadapi.WithAddr("unix:///spire-agent-socket/spire-agent.sock"),
),
)

fmt.Println("After querying the workload api")

if err != nil {
panic("Error acquiring X.509 source")
panic("Error acquiring X.509 source: " + err.Error())
}

defer func(source *workloadapi.X509Source) {
_ = source.Close()
err := source.Close()
if err != nil {
fmt.Println("Error closing X.509 source: " + err.Error())
}
}(source)

authorizer := tlsconfig.AdaptMatcher(func(id spiffeid.ID) error {
Expand All @@ -57,23 +50,16 @@ func main() {
},
}

var counter = 0
var counterLock sync.Mutex

server := &http.Server{
Addr: ":443",
TLSConfig: serverConfig,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counterLock.Lock()
defer counterLock.Unlock()
counter = counter + 1
_, _ = fmt.Fprintf(w, "hello: %d", counter)
handleRequest(w, r, secrets)
}),
}

fmt.Println("Starting server on https://0.0.0.0:443")
if err := server.ListenAndServeTLS("", ""); err != nil {
panic("Error starting server: " + err.Error())
}
fmt.Println("Server started.")
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ WORKDIR /app

COPY go.mod .
COPY go.sum .
COPY client.go .
COPY crypto.go .
COPY network.go .

RUN go mod download

COPY client.go .

RUN CGO_ENABLED=0 GOOS=linux go build -o client client.go
RUN CGO_ENABLED=0 GOOS=linux go build -o client client.go network.go crypto.go

FROM alpine:latest AS client

Expand Down
Loading

0 comments on commit 0bffb89

Please sign in to comment.