Skip to content

Commit

Permalink
Implement mTLS functionality in MCP (istio#7830)
Browse files Browse the repository at this point in the history
* Implement mTLS functionality in MCP.

Add utility code to load&watch certificates and build a TransportCredentials object.
Add an authenticaton check mechanism to admit/fail incoming streams.
Add a basic list-based implementation for authentication check.
Move test certificates from galley/validator folder to pkg/mcp/testing.

* Accommodate CR comments
istio#1
  • Loading branch information
ozevren authored and istio-testing committed Aug 14, 2018
1 parent d0841f8 commit 65419ad
Show file tree
Hide file tree
Showing 14 changed files with 855 additions and 21 deletions.
2 changes: 1 addition & 1 deletion galley/pkg/crd/validation/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"

"istio.io/istio/galley/pkg/crd/validation/testcerts"
"istio.io/istio/pkg/mcp/testing/testcerts"
)

var (
Expand Down
2 changes: 1 addition & 1 deletion galley/pkg/crd/validation/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ import (
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"

"istio.io/istio/galley/pkg/crd/validation/testcerts"
"istio.io/istio/mixer/pkg/config/store"
"istio.io/istio/pilot/pkg/config/kube/crd"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pilot/pkg/model/test"
"istio.io/istio/pilot/test/mock"
"istio.io/istio/pkg/mcp/testing/testcerts"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion galley/pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func newServer(a *Args, p patchTable) (*Server, error) {
distributor := snapshot.New()
s.processor = runtime.NewProcessor(src, distributor)

s.mcp = server.New(distributor, metadata.Types.TypeURLs())
s.mcp = server.New(distributor, metadata.Types.TypeURLs(), nil)

var grpcOptions []grpc.ServerOption
grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(uint32(a.MaxConcurrentStreams)))
Expand Down
2 changes: 1 addition & 1 deletion pilot/pkg/kube/inject/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ import (

"github.com/gogo/protobuf/jsonpb"

"istio.io/istio/galley/pkg/crd/validation/testcerts"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pilot/test/util"
"istio.io/istio/pkg/mcp/testing/testcerts"
)

const (
Expand Down
49 changes: 49 additions & 0 deletions pkg/mcp/creds/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2018 Istio 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 creds

import (
"crypto/tls"

"google.golang.org/grpc/credentials"
)

// CreateForClient creates TransportCredentials for MCP clients.
func CreateForClient(serverName string, watcher *CertificateWatcher) credentials.TransportCredentials {
config := tls.Config{
ServerName: serverName,
RootCAs: watcher.caCertPool,
GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
c := watcher.get()
return &c, nil
},
}

return credentials.NewTLS(&config)
}

// CreateForServer creates TransportCredentials for MCP servers.
func CreateForServer(watcher *CertificateWatcher) credentials.TransportCredentials {
config := tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: watcher.caCertPool,
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
c := watcher.get()
return &c, nil
},
}

return credentials.NewTLS(&config)
}
158 changes: 158 additions & 0 deletions pkg/mcp/creds/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2018 Istio 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 creds

import (
"io/ioutil"
"os"
"path"
"strings"
"testing"

"istio.io/istio/pkg/mcp/testing/testcerts"
)

func TestWatchFiles(t *testing.T) {
var testCases = []struct {
name string
key []byte
cert []byte
cacert []byte
err string
}{
{
name: "basic",
key: testcerts.ServerKey,
cert: testcerts.ServerCert,
cacert: testcerts.CACert,
},
{
name: "rotated",
key: testcerts.RotatedKey,
cert: testcerts.RotatedCert,
cacert: testcerts.RotatedCert,
},
{
name: "badcert",
key: testcerts.ServerKey,
cert: testcerts.BadCert,
cacert: testcerts.CACert,
err: "error loading client certificate files",
},
{
name: "badcacert",
key: testcerts.ServerKey,
cert: testcerts.ServerCert,
cacert: testcerts.BadCert,
err: "failed to append loaded CA certificate",
},
{
name: "noclientcert",
key: testcerts.ServerKey,
cacert: testcerts.CACert,
err: "error loading client certificate files",
},
{
name: "noclientkey",
cert: testcerts.ServerCert,
cacert: testcerts.CACert,
err: "error loading client certificate files",
},
{
name: "nocacert",
key: testcerts.ServerKey,
cert: testcerts.ServerCert,
err: "error loading CA certificate file",
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), testCase.name)
if err != nil {
t.Fatalf("Error creating temp dir: %v", err)
}

defer func() {
_ = os.RemoveAll(dir)
}()

certFile := path.Join(dir, "foo.pem")
keyFile := path.Join(dir, "key.pem")
caCertFile := path.Join(dir, "bar.pem")

if testCase.cert != nil {
if err = ioutil.WriteFile(certFile, testCase.cert, os.ModePerm); err != nil {
t.Fatalf("Error writing cert file: %v", err)
}
}

if testCase.key != nil {
if err = ioutil.WriteFile(keyFile, testCase.key, os.ModePerm); err != nil {
t.Fatalf("Error writing key file: %v", err)
}
}

if testCase.cacert != nil {
if err = ioutil.WriteFile(caCertFile, testCase.cacert, os.ModePerm); err != nil {
t.Fatalf("Error writing CA cert file: %v", err)
}
}

stopCh := make(chan struct{})
defer close(stopCh)
_, err = WatchFiles(stopCh, certFile, keyFile, caCertFile)
if testCase.err != "" && err == nil {
t.Fatalf("Expected error not found: %v", testCase.err)
}

if testCase.err == "" && err != nil {
t.Fatalf("Unexpected error found: %v", err)
}

if err != nil && !strings.HasPrefix(err.Error(), testCase.err) {
t.Fatalf("Error mismatch: got:%v, wanted:%q", err, testCase.err)
}
})
}
}

func TestWatchFolder(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "TestLoadFromFolder")
if err != nil {
t.Fatalf("Error creating temp dir: %v", err)
}

certFile := path.Join(dir, "cert-chain.pem")
keyFile := path.Join(dir, "key.pem")
caCertFile := path.Join(dir, "root-cert.pem")

if err = ioutil.WriteFile(certFile, testcerts.ServerCert, os.ModePerm); err != nil {
t.Fatalf("Error writing cert file: %v", err)
}
if err = ioutil.WriteFile(keyFile, testcerts.ServerKey, os.ModePerm); err != nil {
t.Fatalf("Error writing key file: %v", err)
}
if err = ioutil.WriteFile(caCertFile, testcerts.CACert, os.ModePerm); err != nil {
t.Fatalf("Error writing CA cert file: %v", err)
}

stopCh := make(chan struct{})
defer close(stopCh)
_, err = WatchFolder(stopCh, dir)
if err != nil {
t.Fatalf("Unexpected error found: %v", err)
}
}
Loading

0 comments on commit 65419ad

Please sign in to comment.