Skip to content

Commit

Permalink
examples/advancedtls: example code for different security configurati…
Browse files Browse the repository at this point in the history
…ons for grpc-go using `advancedtls` (#7474)

Add examples of advanced tls usage
  • Loading branch information
gtcooke94 authored Aug 27, 2024
1 parent 0b6f354 commit 005b092
Show file tree
Hide file tree
Showing 21 changed files with 1,580 additions and 0 deletions.
5 changes: 5 additions & 0 deletions examples/examples_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pass () {
EXAMPLES=(
"helloworld"
"route_guide"
"features/advancedtls"
"features/authentication"
"features/authz"
"features/cancellation"
Expand All @@ -75,12 +76,14 @@ EXAMPLES=(
declare -A SERVER_ARGS=(
["features/unix_abstract"]="-addr $UNIX_ADDR"
["default"]="-port $SERVER_PORT"
["features/advancedtls"]="-credentials_directory $(dirname $(realpath "$0"))/features/advancedtls/creds"
)

declare -A CLIENT_ARGS=(
["features/unix_abstract"]="-addr $UNIX_ADDR"
["features/orca"]="-test=true"
["default"]="-addr localhost:$SERVER_PORT"
["features/advancedtls"]="-credentials_directory $(dirname $(realpath "$0"))/features/advancedtls/creds"
)

declare -A SERVER_WAIT_COMMAND=(
Expand Down Expand Up @@ -125,6 +128,7 @@ declare -A EXPECTED_SERVER_OUTPUT=(
["features/orca"]="Server listening"
["features/retry"]="request succeeded count: 4"
["features/unix_abstract"]="serving on @abstract-unix-socket"
["features/advancedtls"]=""
)

declare -A EXPECTED_CLIENT_OUTPUT=(
Expand All @@ -149,6 +153,7 @@ declare -A EXPECTED_CLIENT_OUTPUT=(
["features/orca"]="Per-call load report received: map\[db_queries:10\]"
["features/retry"]="UnaryEcho reply: message:\"Try and Success\""
["features/unix_abstract"]="calling echo.Echo/UnaryEcho to unix-abstract:abstract-unix-socket"
["features/advancedtls"]=""
)

cd ./examples
Expand Down
28 changes: 28 additions & 0 deletions examples/features/advancedtls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# gRPC Advanced Security Examples
This repo contains example code for different security configurations for grpc-go using `advancedtls`.

The servers run a basic echo server with the following setups:
* Port 8885: A server with a good certificate using certificate providers and crl providers.
* Port 8884: A server with a revoked certificate using certificate providers and crl providers.
* Port 8883: A server running using InsecureCredentials.

The clients are designed to call these servers with varying configurations of credentials and revocation configurations.
* mTLS with certificate providers and CRLs
* mTLS with custom verification
* mTLS with credentials from credentials.NewTLS (directly using the tls.Config)
* Insecure Credentials

## Building and Running
```
# Run the server
$ go run server/main.go -credentials_directory $(pwd)/creds
# Run the clients from the `grpc-go/examples/features/advancedtls` directory
$ go run client/main.go -credentials_directory $(pwd)/creds
```

Stop the servers with ctrl-c or by killing the process.

## Developer Note - Generate the credentials used in the examples
The credentials used for these examples were generated by running the `examples/features/advancedtls/generate.sh` script.

If the credentials need to be re-generated, run `./generate.sh` from `/path/to/grpc-go/examples/features/advancedtls` to re-create the `creds` directory containing the certificates and CRLs needed for these examples.
304 changes: 304 additions & 0 deletions examples/features/advancedtls/client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
/*
*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package main

Check failure on line 19 in examples/features/advancedtls/client/main.go

View workflow job for this annotation

GitHub Actions / tests (vet, 1.22)

should have a package comment https://revive.run/r#package-comments

import (
"context"
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"os"
"path/filepath"
"time"

pb "google.golang.org/grpc/examples/features/proto/echo"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/credentials/tls/certprovider"
"google.golang.org/grpc/credentials/tls/certprovider/pemfile"
"google.golang.org/grpc/security/advancedtls"
)

const credRefreshInterval = 1 * time.Minute
const serverAddr = "localhost"
const goodServerPort string = "50051"
const revokedServerPort string = "50053"
const insecurePort string = "50054"
const message string = "Hello"

// -- TLS --

func makeRootProvider(credsDirectory string) certprovider.Provider {
rootOptions := pemfile.Options{
RootFile: filepath.Join(credsDirectory, "ca_cert.pem"),
RefreshDuration: credRefreshInterval,
}
rootProvider, err := pemfile.NewProvider(rootOptions)
if err != nil {
fmt.Printf("Error %v\n", err)
os.Exit(1)
}
return rootProvider
}

func makeIdentityProvider(revoked bool, credsDirectory string) certprovider.Provider {
var certFile string
if revoked {
certFile = filepath.Join(credsDirectory, "client_cert_revoked.pem")
} else {
certFile = filepath.Join(credsDirectory, "client_cert.pem")
}
identityOptions := pemfile.Options{
CertFile: certFile,
KeyFile: filepath.Join(credsDirectory, "client_key.pem"),
RefreshDuration: credRefreshInterval,
}
identityProvider, err := pemfile.NewProvider(identityOptions)
if err != nil {
fmt.Printf("Error %v\n", err)
os.Exit(1)
}
return identityProvider
}

func runClientWithProviders(rootProvider certprovider.Provider, identityProvider certprovider.Provider, crlProvider advancedtls.CRLProvider, port string, shouldFail bool) {
options := &advancedtls.Options{
// Setup the certificates to be used
IdentityOptions: advancedtls.IdentityCertificateOptions{
IdentityProvider: identityProvider,
},
// Setup the roots to be used
RootOptions: advancedtls.RootCertificateOptions{
RootProvider: rootProvider,
},
// Tell the client to verify the server cert
VerificationType: advancedtls.CertVerification,
}

// Configure revocation and CRLs
options.RevocationOptions = &advancedtls.RevocationOptions{
CRLProvider: crlProvider,
}

clientTLSCreds, err := advancedtls.NewClientCreds(options)

if err != nil {
fmt.Printf("Error %v\n", err)
os.Exit(1)
}
fullServerAddr := serverAddr + ":" + port
runWithCredentials(clientTLSCreds, fullServerAddr, !shouldFail)
}

func tlsWithCRLsToGoodServer(credsDirectory string) {
rootProvider := makeRootProvider(credsDirectory)
defer rootProvider.Close()
identityProvider := makeIdentityProvider(false, credsDirectory)
defer identityProvider.Close()
crlProvider := makeCRLProvider(credsDirectory)
defer crlProvider.Close()

runClientWithProviders(rootProvider, identityProvider, crlProvider, goodServerPort, false)
}

func tlsWithCRLsToRevokedServer(credsDirectory string) {
rootProvider := makeRootProvider(credsDirectory)
defer rootProvider.Close()
identityProvider := makeIdentityProvider(false, credsDirectory)
defer identityProvider.Close()
crlProvider := makeCRLProvider(credsDirectory)
defer crlProvider.Close()

runClientWithProviders(rootProvider, identityProvider, crlProvider, revokedServerPort, true)
}

func tlsWithCRLs(credsDirectory string) {
tlsWithCRLsToGoodServer(credsDirectory)
tlsWithCRLsToRevokedServer(credsDirectory)
}

func makeCRLProvider(crlDirectory string) *advancedtls.FileWatcherCRLProvider {
options := advancedtls.FileWatcherOptions{
CRLDirectory: crlDirectory,
}
provider, err := advancedtls.NewFileWatcherCRLProvider(options)
if err != nil {
fmt.Printf("Error making CRL Provider: %v\nExiting...", err)
os.Exit(1)
}
return provider
}

// --- Custom Verification ---
func customVerificaitonSucceed(info *advancedtls.HandshakeVerificationInfo) (*advancedtls.PostHandshakeVerificationResults, error) {
// Looks at info for what you care about as the custom verification implementer
if info.ServerName != "localhost:50051" {
return nil, fmt.Errorf("expected servername of localhost:50051, got %v", info.ServerName)
}
return &advancedtls.PostHandshakeVerificationResults{}, nil
}

func customVerificaitonFail(info *advancedtls.HandshakeVerificationInfo) (*advancedtls.PostHandshakeVerificationResults, error) {
// Looks at info for what you care about as the custom verification implementer
if info.ServerName != "ExampleDesignedToFail" {
return nil, fmt.Errorf("expected servername of ExampleDesignedToFail, got %v", info.ServerName)
}
return &advancedtls.PostHandshakeVerificationResults{}, nil
}

func customVerification(credsDirectory string) {
runClientWithCustomVerification(credsDirectory, goodServerPort)

}

func runClientWithCustomVerification(credsDirectory string, port string) {
rootProvider := makeRootProvider(credsDirectory)
defer rootProvider.Close()
identityProvider := makeIdentityProvider(false, credsDirectory)
defer identityProvider.Close()
fullServerAddr := serverAddr + ":" + port
{
// Run with the custom verification func that will succeed
options := &advancedtls.Options{
// Setup the certificates to be used
IdentityOptions: advancedtls.IdentityCertificateOptions{
IdentityProvider: identityProvider,
},
// Setup the roots to be used
RootOptions: advancedtls.RootCertificateOptions{
RootProvider: rootProvider,
},
// Tell the client to verify the server cert
VerificationType: advancedtls.CertVerification,
AdditionalPeerVerification: customVerificaitonSucceed,
}

clientTLSCreds, err := advancedtls.NewClientCreds(options)

if err != nil {
fmt.Printf("Error %v\n", err)
os.Exit(1)
}
runWithCredentials(clientTLSCreds, fullServerAddr, true)
}
{
// Run with the custom verification func that will fail
options := &advancedtls.Options{
// Setup the certificates to be used
IdentityOptions: advancedtls.IdentityCertificateOptions{
IdentityProvider: identityProvider,
},
// Setup the roots to be used
RootOptions: advancedtls.RootCertificateOptions{
RootProvider: rootProvider,
},
// Tell the client to verify the server cert
VerificationType: advancedtls.CertVerification,
AdditionalPeerVerification: customVerificaitonFail,
}

clientTLSCreds, err := advancedtls.NewClientCreds(options)

if err != nil {
fmt.Printf("Error %v\n", err)
os.Exit(1)
}
runWithCredentials(clientTLSCreds, fullServerAddr, false)
}
}

// -- credentials.NewTLS example --
func credentialsNewTLSExample(credsDirectory string) {
cert, err := tls.LoadX509KeyPair(filepath.Join(credsDirectory, "client_cert.pem"), filepath.Join(credsDirectory, "client_key.pem"))
if err != nil {
os.Exit(1)
}
rootPem, err := os.ReadFile(filepath.Join(credsDirectory, "ca_cert.pem"))
if err != nil {
os.Exit(1)
}
root := x509.NewCertPool()
if !root.AppendCertsFromPEM(rootPem) {
os.Exit(1)
}

config := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: root,
}

// Directly create credentials from a tls.Config.
creds := credentials.NewTLS(config)
port := goodServerPort
fullServerAddr := serverAddr + ":" + port
runWithCredentials(creds, fullServerAddr, true)

}

// -- Insecure --
func insecureCredentialsExample() {
creds := insecure.NewCredentials()
port := insecurePort
fullServerAddr := serverAddr + ":" + port
runWithCredentials(creds, fullServerAddr, true)
}

// -- Main and Runner --

// All of these examples differ in how they configure the
// credentials.TransportCredentials object. Once we have that, actually making
// the calls with gRPC is the same.
func runWithCredentials(creds credentials.TransportCredentials, fullServerAddr string, shouldSucceed bool) {
conn, err := grpc.NewClient(fullServerAddr, grpc.WithTransportCredentials(creds))
if err != nil {
fmt.Printf("Error during grpc.NewClient %v\n", err)
os.Exit(1)
}
defer conn.Close()
client := pb.NewEchoClient(conn)
req := &pb.EchoRequest{
Message: message,
}
context, cancel := context.WithTimeout(context.Background(), 10*time.Second)
resp, err := client.UnaryEcho(context, req)
defer cancel()

if shouldSucceed && err != nil {
fmt.Printf("Error during client.UnaryEcho %v\n", err)
} else if !shouldSucceed && err == nil {
fmt.Printf("Should have failed but didn't, got response: %v\n", resp)
}

}
func main() {
credsDirectory := flag.String("credentials_directory", "", "Path to the creds directory of this example repo")
flag.Parse()

if *credsDirectory == "" {
fmt.Println("Must set credentials_directory argument to this repo's creds directory")
os.Exit(1)
}
tlsWithCRLs(*credsDirectory)
customVerification(*credsDirectory)
credentialsNewTLSExample(*credsDirectory)
insecureCredentialsExample()
}
Loading

0 comments on commit 005b092

Please sign in to comment.