diff --git a/cert-generate.sh b/cert-generate.sh new file mode 100644 index 0000000..0928426 --- /dev/null +++ b/cert-generate.sh @@ -0,0 +1,8 @@ +#!/bin/bash +cd certs +sudo apt-get update +sudo apt-get install openssl +openssl genpkey -algorithm RSA -out server-key.pem +openssl req -new -key server-key.pem -out server-csr.pem +openssl x509 -req -days 90 -in server-csr.pem -signkey server-key.pem -out server-crt.pem +openssl x509 -in server-crt.pem -text -noout diff --git a/certs/README.md b/certs/README.md new file mode 100644 index 0000000..00caa37 --- /dev/null +++ b/certs/README.md @@ -0,0 +1,2 @@ +Committing as development certs. Not to be used for prod. +Though it would be best to deploy and manage the certs using the Let's Encrypt certbot https://letsencrypt.org/getting-started/. \ No newline at end of file diff --git a/certs/server-crt.pem b/certs/server-crt.pem new file mode 100644 index 0000000..b1a4f49 --- /dev/null +++ b/certs/server-crt.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWTCCAkGgAwIBAgIUDwjhycm3QWtMPvCe32TGuu982XwwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCdXMxETAPBgNVBAgMCGlsbGlub2lzMRAwDgYDVQQHDAdj +aGljYWdvMQ0wCwYDVQQKDARzZWxmMRIwEAYDVQQDDAlpbnRlcnZpZXcwHhcNMjQw +NTA1MDEwMDQ5WhcNMjQwODAzMDEwMDQ5WjBVMQswCQYDVQQGEwJ1czERMA8GA1UE +CAwIaWxsaW5vaXMxEDAOBgNVBAcMB2NoaWNhZ28xDTALBgNVBAoMBHNlbGYxEjAQ +BgNVBAMMCWludGVydmlldzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AONuq9FRfTJWMNcjMpWuPIBvbOuwTZQQFjcWSKa617rAaJCtQQfvORVh5heVQ2ci +NytIib9gVt0GVuTNvUjJL9U7IoM0en/wFXZ9a19hTrzkOk4k42HyyVM276JEVhmh +244hgoWpFLOnIBxq3b8iGp46sCuy0p7NeTli42huIpsLADb+vPXuo3z1bE5qI8Nb +0uxiNuL9b4Uzj8m7O2zRi5j+2DOZYwyG/BQ/Cuk+CMekS9XQd6WO5A4ZQXfvgHe2 +hsmEEeCJTDROWjSslI1uhV0PF2MKkfA4aZWzulAtP0LaBewjP7onVxF+eZdAlljX +GAWVJWd1zujsc9Qmuo+T3UsCAwEAAaMhMB8wHQYDVR0OBBYEFAbX51YB3yFo+Imb +h3Cf+pVO/8j2MA0GCSqGSIb3DQEBCwUAA4IBAQCZTj+3VreolprDizt4DhA7qa0S +M7r4t1iRz2X9xTshL+/ljHIitX98kWCC6v5MAJG7JG4dZeK8Xv6DOMLzI7m3Z1Sz +4wvarjLbZ/034MpN+7sivR6/TQm0Ni/agnqhDlMWcY3zVsrmMjSVRE55+sARmUYf +RRXq9dsEIb5CM9+EMViwAozV1LGfRIU94trE4uLd15SoL0f8FSSNl7vt7B9r/RuV +7CPkt8Jo1Lef/kA4AfQ/lv4wxtNrRWhH3IDZ/zcnbqY2cV8YPO5RtHAeUhI04DGK +Eo1Vvd+PBXuYyusqEz/0saLhXdveAoUxnbKhM+N9KKc8Xz+KzZqN7FM0muJR +-----END CERTIFICATE----- diff --git a/certs/server-csr.pem b/certs/server-csr.pem new file mode 100644 index 0000000..4c8c18f --- /dev/null +++ b/certs/server-csr.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICmjCCAYICAQAwVTELMAkGA1UEBhMCdXMxETAPBgNVBAgMCGlsbGlub2lzMRAw +DgYDVQQHDAdjaGljYWdvMQ0wCwYDVQQKDARzZWxmMRIwEAYDVQQDDAlpbnRlcnZp +ZXcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjbqvRUX0yVjDXIzKV +rjyAb2zrsE2UEBY3Fkimute6wGiQrUEH7zkVYeYXlUNnIjcrSIm/YFbdBlbkzb1I +yS/VOyKDNHp/8BV2fWtfYU685DpOJONh8slTNu+iRFYZoduOIYKFqRSzpyAcat2/ +IhqeOrArstKezXk5YuNobiKbCwA2/rz17qN89WxOaiPDW9LsYjbi/W+FM4/Juzts +0YuY/tgzmWMMhvwUPwrpPgjHpEvV0HeljuQOGUF374B3tobJhBHgiUw0Tlo0rJSN +boVdDxdjCpHwOGmVs7pQLT9C2gXsIz+6J1cRfnmXQJZY1xgFlSVndc7o7HPUJrqP +k91LAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAom/CmjoNPof2d5ZPbKCEnEhO +RWPntts/EhqNYJPpronrz2xzw2TgxHiCmr8LtZwCjNy0rdVSAoaqUVH98R4kYk/4 +KrQOVMMehAWkfBRbCYdReS/SQGCRO623R2LdFRHRgfFbosbJXyvWMkLoO1A0xuPH +h0Au+ESiGVjXVPCfahC8YXN3WFSb45iZHsnWjkQwFLYOLk8pZwyQ9Pr9780STQil +Z81rlCZ7IwRKx0ad5sieejS7XQhLqwu3efFhgk/a3PmBpPzSHBfnfHzsTJyn3fOp +oddY4YaukYpQEofacTKaxgMtN+izOgrhAUO2rznm+dr3I41OMnZL7WOWwSI9Gw== +-----END CERTIFICATE REQUEST----- diff --git a/certs/server-key.pem b/certs/server-key.pem new file mode 100644 index 0000000..a808435 --- /dev/null +++ b/certs/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDjbqvRUX0yVjDX +IzKVrjyAb2zrsE2UEBY3Fkimute6wGiQrUEH7zkVYeYXlUNnIjcrSIm/YFbdBlbk +zb1IyS/VOyKDNHp/8BV2fWtfYU685DpOJONh8slTNu+iRFYZoduOIYKFqRSzpyAc +at2/IhqeOrArstKezXk5YuNobiKbCwA2/rz17qN89WxOaiPDW9LsYjbi/W+FM4/J +uzts0YuY/tgzmWMMhvwUPwrpPgjHpEvV0HeljuQOGUF374B3tobJhBHgiUw0Tlo0 +rJSNboVdDxdjCpHwOGmVs7pQLT9C2gXsIz+6J1cRfnmXQJZY1xgFlSVndc7o7HPU +JrqPk91LAgMBAAECggEACw/BbJNVjwKxKm2MUspgJ/EDb60qAMcNhY1gaDeTMYKV +M2Ax1wR0Fs2lpsT8O8Jmw4SqKnQxlVtvE37MM4ORf4H6Sj5drOZM7O/proBpl3dU +PKO/qW5pC1KTMiW3uFRufMm1cvHHz3sT7T3nXhn3CHYbRmzSoKWmvTtJizU8GqVF +Wv5bdiOOrcKbzWOE0wGA2ViPQHrA+4uJhTZSCeihc1CKnKK6sR/G4MLwavRXpFYE +zuafVfsWdjUUeQ+07cNeBMwqdedeDsjM3We810shxgJLRubsat6lOY6OvW8aMjFY +kdcyaj/k2WGOD5iil748h/z/nXkbmHKqZa3cWJyD/QKBgQDy47hOVjlgFznh51dS +jtwThRa2w66x399a7I0hh57xP4ru5E02LiCKlVhjVda45/GJKlZflIZp/dxsQ+HA +qli67CU9SpyiK/QefIXGv10MUMJrMAtwgrJuwPs3Lyfc8LtF4+0CkgBt9G9QbVsy +T0Icqd16GzZ1Zn4eLGC6o2HYlwKBgQDvtVx7Wpi59IHe0ASLXsxMXb6z4aBNbGzR +QUOGVzB8ZZSvbfMzljNdF8r43KD4MnLb+aC0tc2H+WKQ2FItZIiAJ+MBjkSw7Wsq +4IqIpv9v4Ybq1AJwHJorzlpw5gDLtXb+a71EEXBE7h+hOHUOjaGmP3HT+qF8zYsj +tnM7/+IjbQKBgQDwJK3o5d88XjpgW/Y+LfjxY7idYsOqIgoXP6IZ8Jj5NTYME7Uz +SE/sNNR7AjeWAd0RHMbhIVv0F4aDlGnzr3ii9y+qdcZ/oK1wJvWtFy7MKlzO1WW6 +C76XOj4mxXzIOUsvQrbmv6ulCvOztSthhnN7G4daXuVtFbTD6GSKo1buaQKBgQDY +6vv3vLI8hOEJaqDSJkUmTicGzQStS5LlgfPDHB+KUrpMTmkoo6FzetZ4gd9A+xYp +rioZnfOSOsFRZhBnd3R21KF+hOnwWckDEhMLOmQpMKNQ2e4i2h9ByQja5aiOr3Yx +IfoyIL3CAuKomFiFhPFlakBtnX6JW8+vz6lUAGj5uQKBgCsMfkyhMjek0ylW4C4U +EeeqVX8Qytkpqmhbk4vNz0Kbf9hQVtdP2rBt5myySNiMRWSk0sh2W511XHPqWqP9 +XCMBTtc6LR2CR9zNVbSQahwGFPR9sXWP3zqJvLyYnLHxkEtRS/wW2DItLlswXHJU +rfTDNo6F7R1kwI+TK4gDk5Vm +-----END PRIVATE KEY----- diff --git a/client-go/cmd/client/client.go b/client-go/cmd/client/client.go index 40b753d..91a79d1 100644 --- a/client-go/cmd/client/client.go +++ b/client-go/cmd/client/client.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/tls" "encoding/json" "interview-client/internal/consumer" "log" @@ -9,7 +10,7 @@ import ( "github.com/pkg/errors" "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials" ) type config struct { @@ -36,13 +37,17 @@ func main() { ctx := context.Background() config := loadConfig() + // Verify the server cert is in prod. + tlsConfig := &tls.Config{InsecureSkipVerify: true} + creds := credentials.NewTLS(tlsConfig) conn, err := grpc.DialContext( ctx, config.Server, - grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithTransportCredentials(creds), grpc.WithBlock(), ) + if err != nil { log.Fatalln(errors.Wrap(err, "failed to connect to service")) } diff --git a/service-go/cmd/service/service.go b/service-go/cmd/service/service.go index 07b34c8..a817ee7 100644 --- a/service-go/cmd/service/service.go +++ b/service-go/cmd/service/service.go @@ -1,9 +1,8 @@ package main import ( - "context" + "crypto/tls" "fmt" - "os" "log" "net" @@ -11,13 +10,14 @@ import ( config "interview-service/config" "interview-service/internal/api" "interview-service/internal/api/interview" - jwt "interview-service/internal/domain/jwt" - grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "google.golang.org/grpc" - "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" - "google.golang.org/grpc/status" +) + +const ( + configPath = "./config/grpc.json" ) func main() { @@ -31,16 +31,13 @@ func main() { log.Fatalf("failed to listen: %v", err) } - var jwtSecret = os.Getenv("JWT_SECRET") - - if jwtSecret == "" { - log.Fatalf("error loading secret from envoirnment") + tlsCert, err := tls.LoadX509KeyPair("../certs/server-crt.pem", "../certs/server-key.pem") + if err != nil { + log.Fatalf("failed to load server TLS certificate: %v", err) } opts := []grpc.ServerOption{ - grpc.UnaryInterceptor( - grpc_auth.UnaryServerInterceptor(validateJWT([]byte(jwtSecret))), - ), + grpc.Creds(credentials.NewServerTLSFromCert(&tlsCert)), } grpcServer := grpc.NewServer(opts...) @@ -52,30 +49,3 @@ func main() { grpcServer.Serve(lis) } - -const ( - authHeader = "authorization" - configPath = "./config/grpc.json" -) - -// validateJWT parses and validates a bearer jwt -// -// TODO: move to own package (in ./internal/api/auth) using a constructor that privately sets the secret -func validateJWT(secret []byte) func(ctx context.Context) (context.Context, error) { - return func(ctx context.Context) (context.Context, error) { - token, err := grpc_auth.AuthFromMD(ctx, "bearer") - if err != nil { - return nil, err - } - - claims, err := jwt.ValidateToken(token, secret) - if err != nil { - log.Default().Println(err) - return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err) - } - - ctx = context.WithValue(ctx, authHeader, claims) - - return ctx, nil - } -} diff --git a/service-go/internal/domain/jwt/jwt.go b/service-go/internal/domain/jwt/jwt.go deleted file mode 100644 index b38299f..0000000 --- a/service-go/internal/domain/jwt/jwt.go +++ /dev/null @@ -1,72 +0,0 @@ -package jwtValidator - -import ( - "errors" - "github.com/dgrijalva/jwt-go" - "time" -) - -// JWTClaims represents the custom claims for our JWT -type JWTClaims struct { - Username string `json:"username"` - jwt.StandardClaims -} - -type JWTlib interface { - GenerateToken(username string, expiresIn time.Duration, secret []byte) (string, error) - ValidateToken(tokenString string, secret []byte) (*JWTClaims, error) -} - -var ErrInvalidToken = errors.New("invalid token") - -// GenerateToken generates a JWT token with the provided username and expiration time -func GenerateToken(username string, expiresIn time.Duration, secret []byte) (string, error) { - // Create a new token object - token := jwt.New(jwt.SigningMethodHS256) - - // Set the claims for the token - claims := JWTClaims{ - username, - jwt.StandardClaims{ - ExpiresAt: time.Now().Add(expiresIn).Unix(), - IssuedAt: time.Now().Unix(), - }, - } - token.Claims = claims - - // Sign the token with the provided secret - tokenString, err := token.SignedString(secret) - if err != nil { - return "", err - } - - return tokenString, nil -} - -// ValidateToken validates a JWT token with the provided secret and returns the claims -func ValidateToken(tokenString string, secret []byte) (*JWTClaims, error) { - // Parse the token - token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { - // Validate the signing method - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, jwt.ErrSignatureInvalid - } - return secret, nil - }) - - // Check if the token is valid - if err != nil { - if err == jwt.ErrSignatureInvalid { - return nil, ErrInvalidToken - } - return nil, err - } - - // Extract the claims from the token - claims, ok := token.Claims.(*JWTClaims) - if !ok || !token.Valid { - return nil, ErrInvalidToken - } - - return claims, nil -}