Skip to content

Commit

Permalink
Fix allocator service tls auth for C# client and add a C# sample
Browse files Browse the repository at this point in the history
  • Loading branch information
pooneh-m committed May 7, 2020
1 parent ad4e127 commit b216282
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 12 deletions.
42 changes: 31 additions & 11 deletions cmd/allocator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,22 +185,42 @@ func (h *serviceHandler) getServerOptions() []grpc.ServerOption {
}

cfg := &tls.Config{
Certificates: []tls.Certificate{tlsCer},
ClientAuth: tls.RequireAndVerifyClientCert,
GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
h.certMutex.RLock()
defer h.certMutex.RUnlock()
return &tls.Config{
Certificates: []tls.Certificate{tlsCer},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: h.caCertPool,
}, nil
},
Certificates: []tls.Certificate{tlsCer},
ClientAuth: tls.RequireAnyClientCert,
VerifyPeerCertificate: h.verifyClientCertificate,
}
// Add options for creds and OpenCensus stats handler to enable stats and tracing.
return []grpc.ServerOption{grpc.Creds(credentials.NewTLS(cfg)), grpc.StatsHandler(&ocgrpc.ServerHandler{})}
}

// verifyClientCertificate verifies that the client certificate is accepted
// This method is used as GetConfigForClient is cross lang incompatible.
func (h *serviceHandler) verifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
opts := x509.VerifyOptions{
Roots: h.caCertPool,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}

for _, cert := range rawCerts[1:] {
opts.Intermediates.AppendCertsFromPEM(cert)
}

c, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return errors.New("bad client certificate: " + err.Error())
}

h.certMutex.RLock()
defer h.certMutex.RUnlock()
_, err = c.Verify(opts)
if err != nil {
return errors.New("failed to verify client certificate: " + err.Error())
}
return nil
}

// Set up our client which we will use to call the API
func getClients() (*kubernetes.Clientset, *versioned.Clientset, error) {
// Create the in-cluster config
Expand Down
34 changes: 34 additions & 0 deletions cmd/allocator/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ package main

import (
"context"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"net/http"
"os"
Expand Down Expand Up @@ -137,6 +139,38 @@ func TestBadReturnType(t *testing.T) {
assert.Contains(t, st.Message(), "internal server error")
}

func TestVerifyClientCertificateSucceeds(t *testing.T) {
t.Parallel()

crt := []byte(clientCert)
certPool := x509.NewCertPool()
assert.True(t, certPool.AppendCertsFromPEM(crt))

h := serviceHandler{
caCertPool: certPool,
}

block, _ := pem.Decode(crt)
input := [][]byte{block.Bytes}
assert.Nil(t, h.verifyClientCertificate(input, nil),
"verifyClientCertificate failed.")
}

func TestVerifyClientCertificateFails(t *testing.T) {
t.Parallel()

crt := []byte(clientCert)
certPool := x509.NewCertPool()
h := serviceHandler{
caCertPool: certPool,
}

block, _ := pem.Decode(crt)
input := [][]byte{block.Bytes}
assert.Error(t, h.verifyClientCertificate(input, nil),
"verifyClientCertificate() succeeded, expected error.")
}

func TestGettingCaCert(t *testing.T) {
t.Parallel()

Expand Down
45 changes: 45 additions & 0 deletions examples/allocator-client-csharp/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using Grpc.Core;
using V1Alpha1;
using System.Net.Http;

namespace AllocatorClient
{
class Program
{
static async Task Main(string[] args)
{
if (args.Length < 6) {
throw new Exception("Arguments are missing. Expecting: <private key> <public key> <server CA> <external IP> <namepace> <enable multi-cluster>");
}

string clientKey = File.ReadAllText(args[0]);
string clientCert = File.ReadAllText(args[1]);
string serverCa = File.ReadAllText(args[2]);
string externalIp = args[3];
string namespaceArg = args[4];
bool multicluster = bool.Parse(args[5]);

var creds = new SslCredentials(serverCa, new KeyCertificatePair(clientCert, clientKey));
var channel = new Channel(externalIp + ":443", creds);
var client = new AllocationService.AllocationServiceClient(channel);

try {
var response = await client.AllocateAsync(new AllocationRequest {
Namespace = namespaceArg,
MultiClusterSetting = new V1Alpha1.MultiClusterSetting {
Enabled = multicluster,
}
});
Console.WriteLine(response);
}
catch(RpcException e)
{
Console.WriteLine($"gRPC error: {e}");
}
}
}
}
19 changes: 19 additions & 0 deletions examples/allocator-client-csharp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# A sample Allocator service C# client

This sample serves as a gRPC C# client sample code for agones-allocator gRPC service.

Follow instructions in [Allocator Service](https://agones.dev/site/docs/advanced/allocator-service) to set up client and server certificate.

Run the following to allocate a game server:
```
#!/bin/bash
NAMESPACE=default # replace with any namespace
EXTERNAL_IP=`kubectl get services agones-allocator -n agones-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
KEY_FILE=client.key
CERT_FILE=client.crt
TLS_CA_FILE=ca.crt
MULTICLUSTER_ENABLED=false
dotnet run $KEY_FILE $CERT_FILE $TLS_CA_FILE $EXTERNAL_IP $NAMESPACE $MULTICLUSTER_ENABLED
```
19 changes: 19 additions & 0 deletions examples/allocator-client-csharp/allocator-client-csharp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>AllocatorClient</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Api.CommonProtos" Version="1.7.0" />
<PackageReference Include="Google.Protobuf" Version="3.11.2" />
<PackageReference Include="Grpc" Version="2.26.0" />
<PackageReference Include="Grpc.Core" Version="2.26.0" />
<PackageReference Include="Grpc.Tools" Version="2.26.0" PrivateAssets="all"/>
</ItemGroup>

<ItemGroup>
<Protobuf Include="allocation.proto" ProtoRoot="../../proto/allocation/v1alpha1;../../proto/googleapis" GrpcServices="Client"/>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion examples/allocator-client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func main() {
cacertFile := flag.String("cacert", "missing cacert", "the CA cert file for server signing certificate in PEM format")
externalIP := flag.String("ip", "missing external IP", "the external IP for allocator server")
namespace := flag.String("namespace", "default", "the game server kubernetes namespace")
multicluster := flag.Bool("multicluster", false, "enabling")
multicluster := flag.Bool("multicluster", false, "set to true to enable the multi-cluster allocation")

flag.Parse()

Expand Down
7 changes: 7 additions & 0 deletions site/content/en/docs/Advanced/allocator-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,14 @@ kubectl get pods -n agones-system -o=name | grep agones-allocator | xargs kubect

## Send allocation request

{{% feature expiryVersion="1.6.0" %}}
Now the service is ready to accept requests from the client with the generated certificates. Create a [fleet](https://agones.dev/site/docs/getting-started/create-fleet/#1-create-a-fleet) and send a gRPC request to agones-allocator by providing the namespace to which the fleet is deployed. You can find the gRPC sample for sending allocation request at {{< ghlink href="examples/allocator-client/main.go" >}}allocator-client sample{{< /ghlink >}}.
{{% /feature %}}

{{% feature publishVersion="1.6.0" %}}
Now the service is ready to accept requests from the client with the generated certificates. Create a [fleet](https://agones.dev/site/docs/getting-started/create-fleet/#1-create-a-fleet) and send a gRPC request to agones-allocator. To start, take a look at the allocation gRPC client examples in {{< ghlink href="examples/allocator-client/main.go" >}}golang{{< /ghlink >}} and {{< ghlink href="examples/allocator-client-csharp/Program.cs" >}}C#{{< /ghlink >}} languages. In the following, the {{< ghlink href="examples/allocator-client/main.go" >}}golang gRPC client example{{< /ghlink >}} is used to allocate a Game Server in the default namespace.
{{% /feature %}}


```bash
#!/bin/bash
Expand Down

0 comments on commit b216282

Please sign in to comment.