Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples/advancedtls: example code for different security configurations for grpc-go using advancedtls #7474

Merged
merged 18 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

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
Loading