Skip to content

Commit

Permalink
advancedtls: add examples for reloading from file system (grpc#3976)
Browse files Browse the repository at this point in the history
* add examples for reloading from file system
  • Loading branch information
ZhenLian authored and davidkhala committed Dec 7, 2020
1 parent a223251 commit 18a4d41
Show file tree
Hide file tree
Showing 8 changed files with 738 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -35,7 +35,7 @@ install:

script:
- set -e
- if [[ -n "${TESTEXTRAS}" ]]; then examples/examples_test.sh; interop/interop_test.sh; make testsubmodule; exit 0; fi
- if [[ -n "${TESTEXTRAS}" ]]; then examples/examples_test.sh; security/advancedtls/examples/examples_test.sh; interop/interop_test.sh; make testsubmodule; exit 0; fi
- if [[ -n "${VET}" ]]; then ./vet.sh; fi
- if [[ -n "${GAE}" ]]; then make testappengine; exit 0; fi
- if [[ -n "${RACE}" ]]; then make testrace; exit 0; fi
Expand Down
@@ -0,0 +1,26 @@
# Credential Reloading From Files

Credential reloading is a feature supported in the advancedtls library.
A very common way to achieve this is to reload from files.

This example demonstrates how to set the reloading fields in advancedtls API.
Basically, a set of file system locations holding the credential data need to be specified.
Once the credential data needs to be updated, users just change the credential data in the file system, and gRPC will pick up the changes automatically.

This example only shows how to set the API, without demonstrating the way to reload credentials on file system.
To learn more about how to do that in Go, please see `advancedtls_integration_test.go`.

A couple of things to note:
1. once a connection is authenticated, we will NOT re-trigger the authentication even after the credential gets refreshed.
2. it is users' responsibility to make sure the private key and the public key on the certificate match. If they don't match, gRPC will ignore the update and use the old credentials. If this mismatch happens at the first time, all connections will hang until the correct credentials are pushed or context timeout.

## Try it
In directory `security/advancedtls/examples`:

```
go run server/main.go
```

```
go run client/main.go
```
@@ -0,0 +1,99 @@
/*
*
* Copyright 2020 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.
*
*/

// The client demonstrates how to use the credential reloading feature in
// advancedtls to make a mTLS connection to the server.
package main

import (
"context"
"flag"
"fmt"
"log"
"time"

"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc/security/advancedtls"
"google.golang.org/grpc/security/advancedtls/testdata"
)

var address = "localhost:50051"

const (
// Default timeout for normal connections.
defaultConnTimeout = 10 * time.Second
// Intervals that set to monitor the credential updates.
credRefreshingInterval = 1 * time.Minute
)

func main() {
flag.Parse()

// TODO(ZhenLian): change function signatures to reflect the changes in
// https://github.com/grpc/grpc-go/pull/3981.
identityOptions := advancedtls.PEMFileProviderOptions{
CertFile: testdata.Path("client_cert_1.pem"),
KeyFile: testdata.Path("client_key_1.pem"),
IdentityInterval: credRefreshingInterval,
}
identityProvider, err := advancedtls.NewPEMFileProvider(identityOptions)
if err != nil {
log.Fatalf("advancedtls.NewPEMFileProvider(%v) failed: %v", identityOptions, err)
}
rootOptions := advancedtls.PEMFileProviderOptions{
TrustFile: testdata.Path("client_trust_cert_1.pem"),
RootInterval: credRefreshingInterval,
}
rootProvider, err := advancedtls.NewPEMFileProvider(rootOptions)
if err != nil {
log.Fatalf("advancedtls.NewPEMFileProvider(%v) failed: %v", rootOptions, err)
}

options := &advancedtls.ClientOptions{
IdentityOptions: advancedtls.IdentityCertificateOptions{
IdentityProvider: identityProvider,
},
VerifyPeer: func(params *advancedtls.VerificationFuncParams) (*advancedtls.VerificationResults, error) {
return &advancedtls.VerificationResults{}, nil
},
RootOptions: advancedtls.RootCertificateOptions{
RootProvider: rootProvider,
},
VType: advancedtls.CertVerification,
}
clientTLSCreds, err := advancedtls.NewClientCreds(options)
if err != nil {
log.Fatalf("advancedtls.NewClientCreds(%v) failed: %v", options, err)
}

// At initialization, the connection should be good.
ctx, cancel := context.WithTimeout(context.Background(), defaultConnTimeout)
defer cancel()
conn, err := grpc.DialContext(ctx, address, grpc.WithTransportCredentials(clientTLSCreds))
if err != nil {
log.Fatalf("grpc.DialContext to %s failed: %v", address, err)
}
greetClient := pb.NewGreeterClient(conn)
reply, err := greetClient.SayHello(ctx, &pb.HelloRequest{Name: "gRPC"}, grpc.WaitForReady(true))
if err != nil {
log.Fatalf("greetClient.SayHello failed: %v", err)
}
defer conn.Close()
fmt.Printf("Getting message from server: %s...\n", reply.Message)
}
@@ -0,0 +1,105 @@
/*
*
* Copyright 2020 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.
*
*/

// The server demonstrates how to use the credential reloading feature in
// advancedtls to serve mTLS connections from the client.
package main

import (
"context"
"flag"
"fmt"
"log"
"net"
"time"

"google.golang.org/grpc"
"google.golang.org/grpc/security/advancedtls"
"google.golang.org/grpc/security/advancedtls/testdata"

pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

var port = ":50051"

// Intervals that set to monitor the credential updates.
const credRefreshingInterval = 1 * time.Minute

type greeterServer struct {
pb.UnimplementedGreeterServer
}

// sayHello is a simple implementation of the pb.GreeterServer SayHello method.
func (greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
flag.Parse()
fmt.Printf("server starting on port %s...\n", port)

// TODO(ZhenLian): change function signatures to reflect the changes in
// https://github.com/grpc/grpc-go/pull/3981.
identityOptions := advancedtls.PEMFileProviderOptions{
CertFile: testdata.Path("server_cert_1.pem"),
KeyFile: testdata.Path("server_key_1.pem"),
IdentityInterval: credRefreshingInterval,
}
identityProvider, err := advancedtls.NewPEMFileProvider(identityOptions)
if err != nil {
log.Fatalf("advancedtls.NewPEMFileProvider(%v) failed: %v", identityOptions, err)
}
defer identityProvider.Close()
rootOptions := advancedtls.PEMFileProviderOptions{
TrustFile: testdata.Path("server_trust_cert_1.pem"),
RootInterval: credRefreshingInterval,
}
rootProvider, err := advancedtls.NewPEMFileProvider(rootOptions)
if err != nil {
log.Fatalf("advancedtls.NewPEMFileProvider(%v) failed: %v", rootOptions, err)
}
defer rootProvider.Close()

// Start a server and create a client using advancedtls API with Provider.
options := &advancedtls.ServerOptions{
IdentityOptions: advancedtls.IdentityCertificateOptions{
IdentityProvider: identityProvider,
},
RootOptions: advancedtls.RootCertificateOptions{
RootProvider: rootProvider,
},
RequireClientCert: true,
VerifyPeer: func(params *advancedtls.VerificationFuncParams) (*advancedtls.VerificationResults, error) {
return &advancedtls.VerificationResults{}, nil
},
VType: advancedtls.CertVerification,
}
serverTLSCreds, err := advancedtls.NewServerCreds(options)
if err != nil {
log.Fatalf("advancedtls.NewServerCreds(%v) failed: %v", options, err)
}
s := grpc.NewServer(grpc.Creds(serverTLSCreds))
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
pb.RegisterGreeterServer(s, greeterServer{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
126 changes: 126 additions & 0 deletions security/advancedtls/examples/examples_test.sh
@@ -0,0 +1,126 @@
#!/bin/bash
#
# Copyright 2020 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.
#

set +e

export TMPDIR=$(mktemp -d)
trap "rm -rf ${TMPDIR}" EXIT

clean () {
for i in {1..10}; do
jobs -p | xargs -n1 pkill -P
# A simple "wait" just hangs sometimes. Running `jobs` seems to help.
sleep 1
if jobs | read; then
return
fi
done
echo "$(tput setaf 1) clean failed to kill tests $(tput sgr 0)"
jobs
pstree
exit 1
}

fail () {
echo "$(tput setaf 1) $1 $(tput sgr 0)"
clean
exit 1
}

pass () {
echo "$(tput setaf 2) $1 $(tput sgr 0)"
}

EXAMPLES=(
"credential_reloading_from_files"
)

declare -A EXPECTED_SERVER_OUTPUT=(
["credential_reloading_from_files"]=""
)

declare -A EXPECTED_CLIENT_OUTPUT=(
["credential_reloading_from_files"]="Getting message from server: Hello gRPC..."
)

cd ./security/advancedtls/examples

for example in ${EXAMPLES[@]}; do
echo "$(tput setaf 4) testing: ${example} $(tput sgr 0)"

# Build server
if ! go build -o /dev/null ./${example}/*server/*.go; then
fail "failed to build server"
else
pass "successfully built server"
fi

# Build client
if ! go build -o /dev/null ./${example}/*client/*.go; then
fail "failed to build client"
else
pass "successfully built client"
fi

# Start server
SERVER_LOG="$(mktemp)"
go run ./$example/*server/*.go &> $SERVER_LOG &

CLIENT_LOG="$(mktemp)"
if ! timeout 20 go run ${example}/*client/*.go &> $CLIENT_LOG; then
fail "client failed to communicate with server
got server log:
$(cat $SERVER_LOG)
got client log:
$(cat $CLIENT_LOG)
"
else
pass "client successfully communitcated with server"
fi

# Check server log for expected output if expecting an
# output
if [ -n "${EXPECTED_SERVER_OUTPUT[$example]}" ]; then
if ! grep -q "${EXPECTED_SERVER_OUTPUT[$example]}" $SERVER_LOG; then
fail "server log missing output: ${EXPECTED_SERVER_OUTPUT[$example]}
got server log:
$(cat $SERVER_LOG)
got client log:
$(cat $CLIENT_LOG)
"
else
pass "server log contains expected output: ${EXPECTED_SERVER_OUTPUT[$example]}"
fi
fi

# Check client log for expected output if expecting an
# output
if [ -n "${EXPECTED_CLIENT_OUTPUT[$example]}" ]; then
if ! grep -q "${EXPECTED_CLIENT_OUTPUT[$example]}" $CLIENT_LOG; then
fail "client log missing output: ${EXPECTED_CLIENT_OUTPUT[$example]}
got server log:
$(cat $SERVER_LOG)
got client log:
$(cat $CLIENT_LOG)
"
else
pass "client log contains expected output: ${EXPECTED_CLIENT_OUTPUT[$example]}"
fi
fi
clean
echo ""
done
9 changes: 9 additions & 0 deletions security/advancedtls/examples/go.mod
@@ -0,0 +1,9 @@
module google.golang.org/grpc/security/advancedtls/examples

go 1.15

require (
google.golang.org/grpc v1.33.1
google.golang.org/grpc/examples v0.0.0-20201020200225-9519efffeb5d
google.golang.org/grpc/security/advancedtls v0.0.0-20201020200225-9519efffeb5d
)

0 comments on commit 18a4d41

Please sign in to comment.