Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/contour: add SecretCache and link to grpc
Browse files Browse the repository at this point in the history
Addresses the second high-level design bullet point, creating a
SecretCache to be used by Contour to internally identify any changes.
The cache is also registered as a reource type for the gRPC server,
allowing it to respond to requests.

Updates: projectcontour#898
Signed-off-by: Matt Alberts <[email protected]>
Matt Alberts committed Apr 30, 2019
1 parent 7b7a36e commit c7ba7f4
Showing 9 changed files with 833 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/contour/contour.go
Original file line number Diff line number Diff line change
@@ -222,12 +222,14 @@ func main() {
clusterType = typePrefix + "Cluster"
routeType = typePrefix + "RouteConfiguration"
listenerType = typePrefix + "Listener"
secretType = typePrefix + "auth.Secret"
)
s := grpc.NewAPI(log, map[string]grpc.Cache{
clusterType: &ch.ClusterCache,
routeType: &ch.RouteCache,
listenerType: &ch.ListenerCache,
endpointType: et,
secretType: &ch.SecretCache,
})
log.Println("started")
defer log.Println("stopped")
7 changes: 7 additions & 0 deletions internal/contour/cachehandler.go
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ type CacheHandler struct {
ListenerCache
RouteCache
ClusterCache
SecretCache

IngressRouteStatus *k8s.IngressRouteStatus
logrus.FieldLogger
@@ -45,6 +46,7 @@ func (ch *CacheHandler) OnChange(b *dag.Builder) {
defer timer.ObserveDuration()
dag := b.Build()
ch.setIngressRouteStatus(dag)
ch.updateSecrets(dag)
ch.updateListeners(dag)
ch.updateRoutes(dag)
ch.updateClusters(dag)
@@ -60,6 +62,11 @@ func (ch *CacheHandler) setIngressRouteStatus(st statusable) {
}
}

func (ch *CacheHandler) updateSecrets(root dag.Visitable) {
secrets := visitSecrets(root)
ch.SecretCache.Update(secrets)
}

func (ch *CacheHandler) updateListeners(root dag.Visitable) {
listeners := visitListeners(root, &ch.ListenerVisitorConfig)
ch.ListenerCache.Update(listeners)
111 changes: 111 additions & 0 deletions internal/contour/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright © 2018 Heptio
// 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 contour

import (
"sync"

"github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
"github.com/gogo/protobuf/proto"
"github.com/heptio/contour/internal/dag"
"github.com/heptio/contour/internal/envoy"
)

// SecretCache manages the contents of the gRPC SDS cache.
type SecretCache struct {
mu sync.Mutex
values map[string]*auth.Secret
waiters []chan int
last int
}

// Register registers ch to receive a value when Notify is called.
// The value of last is the count of the times Notify has been called on this Cache.
// It functions of a sequence counter, if the value of last supplied to Register
// is less than the Cache's internal counter, then the caller has missed at least
// one notification and will fire immediately.
//
// Sends by the broadcaster to ch must not block, therefor ch must have a capacity
// of at least 1.
func (c *SecretCache) Register(ch chan int, last int) {
c.mu.Lock()
defer c.mu.Unlock()

if last < c.last {
// notify this channel immediately
ch <- c.last
return
}
c.waiters = append(c.waiters, ch)
}

// Update replaces the contents of the cache with the supplied map.
func (c *SecretCache) Update(v map[string]*auth.Secret) {
c.mu.Lock()
defer c.mu.Unlock()

c.values = v
c.notify()
}

// notify notifies all registered waiters that an event has occurred.
func (c *SecretCache) notify() {
c.last++

for _, ch := range c.waiters {
ch <- c.last
}
c.waiters = c.waiters[:0]
}

// Values returns a slice of the value stored in the cache.
func (c *SecretCache) Values(filter func(string) bool) []proto.Message {
c.mu.Lock()
values := make([]proto.Message, 0, len(c.values))
for _, v := range c.values {
if filter(v.Name) {
values = append(values, v)
}
}
c.mu.Unlock()
return values
}

type secretVisitor struct {
secrets map[string]*auth.Secret
}

// visitSecrets produces a map of *auth.Secret
func visitSecrets(root dag.Vertex) map[string]*auth.Secret {
sv := secretVisitor{
secrets: make(map[string]*auth.Secret),
}
sv.visit(root)
return sv.secrets
}

func (v *secretVisitor) visit(vertex dag.Vertex) {
switch svh := vertex.(type) {
case *dag.SecureVirtualHost:
if svh.Secret != nil {
name := envoy.Secretname(svh.Secret)
if _, ok := v.secrets[name]; !ok {
s := envoy.Secret(svh.Secret)
v.secrets[s.Name] = s
}
}
default:
vertex.Visit(v.visit)
}
}
487 changes: 487 additions & 0 deletions internal/contour/secret_test.go

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions internal/contour/visitor_test.go
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import (

"github.com/envoyproxy/go-control-plane/envoy/api/v2"
"github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
"github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
"github.com/google/go-cmp/cmp"
"github.com/heptio/contour/internal/dag"
@@ -155,6 +156,74 @@ func TestVisitListeners(t *testing.T) {
}
}

func TestVisitSecrets(t *testing.T) {
tests := map[string]struct {
root dag.Visitable
want map[string]*auth.Secret
}{
"TCPService forward": {
root: &dag.Listener{
Port: 443,
VirtualHosts: virtualhosts(
&dag.SecureVirtualHost{
VirtualHost: dag.VirtualHost{
Name: "www.example.com",
TCPProxy: &dag.TCPProxy{
Clusters: []*dag.Cluster{{
Upstream: &dag.TCPService{
Name: "example",
Namespace: "default",
ServicePort: &v1.ServicePort{
Protocol: "TCP",
Port: 443,
TargetPort: intstr.FromInt(8443),
},
},
}},
},
},
Secret: &dag.Secret{
Object: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Data: secretdata("certificate", "key"),
},
},
},
),
},
want: secretmap(&auth.Secret{
Name: "default/secret/735ad571c1",
Type: &auth.Secret_TlsCertificate{
TlsCertificate: &auth.TlsCertificate{
PrivateKey: &core.DataSource{
Specifier: &core.DataSource_InlineBytes{
InlineBytes: []byte("key"),
},
},
CertificateChain: &core.DataSource{
Specifier: &core.DataSource_InlineBytes{
InlineBytes: []byte("certificate"),
},
},
},
},
}),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := visitSecrets(tc.root)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatal(diff)
}
})
}
}

func virtualhosts(vx ...dag.Vertex) map[string]dag.Vertex {
m := make(map[string]dag.Vertex)
for _, v := range vx {
8 changes: 8 additions & 0 deletions internal/dag/dag.go
Original file line number Diff line number Diff line change
@@ -293,6 +293,14 @@ func (s *Secret) Data() map[string][]byte {
return s.Object.Data
}

func (s *Secret) Cert() []byte {
return []byte(s.Object.Data[v1.TLSCertKey])
}

func (s *Secret) PrivateKey() []byte {
return []byte(s.Object.Data[v1.TLSPrivateKeyKey])
}

func (s *Secret) toMeta() meta {
return meta{
name: s.Name(),
39 changes: 39 additions & 0 deletions internal/envoy/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package envoy

import (
"crypto/sha1"
"fmt"

"github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
"github.com/heptio/contour/internal/dag"
)

// Secretname returns the name of the SDS secret for this secret.
func Secretname(s *dag.Secret) string {
hash := sha1.Sum(s.Cert())
ns := s.Namespace()
name := s.Name()
return hashname(60, ns, name, fmt.Sprintf("%x", hash[:5]))
}

// Secret creates new v2auth.Secret from secret.
func Secret(s *dag.Secret) *auth.Secret {
return &auth.Secret{
Name: Secretname(s),
Type: &auth.Secret_TlsCertificate{
TlsCertificate: &auth.TlsCertificate{
PrivateKey: &core.DataSource{
Specifier: &core.DataSource_InlineBytes{
InlineBytes: s.PrivateKey(),
},
},
CertificateChain: &core.DataSource{
Specifier: &core.DataSource_InlineBytes{
InlineBytes: s.Cert(),
},
},
},
},
}
}
107 changes: 107 additions & 0 deletions internal/envoy/secret_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package envoy

import (
"testing"

"github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
"github.com/google/go-cmp/cmp"
"github.com/heptio/contour/internal/dag"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestSecret(t *testing.T) {
tests := map[string]struct {
secret *dag.Secret
want *auth.Secret
}{
"simple secret": {
secret: &dag.Secret{
Object: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "simple",
Namespace: "default",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte("cert"),
v1.TLSPrivateKeyKey: []byte("key"),
},
},
},
want: &auth.Secret{
Name: "default/simple/cd1b506996",
Type: &auth.Secret_TlsCertificate{
TlsCertificate: &auth.TlsCertificate{
PrivateKey: &core.DataSource{
Specifier: &core.DataSource_InlineBytes{
InlineBytes: []byte("key"),
},
},
CertificateChain: &core.DataSource{
Specifier: &core.DataSource_InlineBytes{
InlineBytes: []byte("cert"),
},
},
},
},
},
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := Secret(tc.secret)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatal(diff)
}
})
}
}

func TestSecretname(t *testing.T) {
tests := map[string]struct {
secret *dag.Secret
want string
}{
"simple": {
secret: &dag.Secret{
Object: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "simple",
Namespace: "default",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte("cert"),
v1.TLSPrivateKeyKey: []byte("key"),
},
},
},
want: "default/simple/cd1b506996",
},
"far too long": {
secret: &dag.Secret{
Object: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "must-be-in-want-of-a-wife",
Namespace: "it-is-a-truth-universally-acknowledged-that-a-single-man-in-possession-of-a-good-fortune",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte("cert"),
v1.TLSPrivateKeyKey: []byte("key"),
},
},
},
want: "it-is-a-truth-7e164b/must-be-in-wa-7e164b/cd1b506996",
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := Secretname(tc.secret)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatal(diff)
}
})
}
}
3 changes: 3 additions & 0 deletions internal/grpc/server.go
Original file line number Diff line number Diff line change
@@ -58,6 +58,9 @@ func NewAPI(log logrus.FieldLogger, cacheMap map[string]Cache) *grpc.Server {
routeType: &RDS{
Cache: cacheMap[routeType],
},
secretType: &SDS{
Cache: cacheMap[secretType],
},
},
},
}

0 comments on commit c7ba7f4

Please sign in to comment.