Skip to content

Generate a default SSL server for select HTTPS listener hostname #173

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

Merged
merged 2 commits into from
Aug 10, 2022
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
29 changes: 18 additions & 11 deletions internal/nginx/config/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,22 @@ func generateDefaultHTTPServer() server {
func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore) (server, Warnings) {
warnings := newWarnings()

locs := make([]location, 0, len(virtualServer.PathRules)) // FIXME(pleshakov): expand with rule.Routes
s := server{ServerName: virtualServer.Hostname}

if virtualServer.SSL != nil {
s.SSL = &ssl{
Certificate: virtualServer.SSL.CertificatePath,
CertificateKey: virtualServer.SSL.CertificatePath,
}
}

if len(virtualServer.PathRules) == 0 {
// generate default "/" 404 location
s.Locations = []location{{Path: "/", Return: &returnVal{Code: statusNotFound}}}
return s, warnings
}

locs := make([]location, 0, len(virtualServer.PathRules)) // FIXME(pleshakov): expand with rule.Routes
for _, rule := range virtualServer.PathRules {
matches := make([]httpMatch, 0, len(rule.MatchRules))

Expand Down Expand Up @@ -125,16 +139,9 @@ func generate(virtualServer state.VirtualServer, serviceStore state.ServiceStore
locs = append(locs, pathLoc)
}
}
s := server{
ServerName: virtualServer.Hostname,
Locations: locs,
}
if virtualServer.SSL != nil {
s.SSL = &ssl{
Certificate: virtualServer.SSL.CertificatePath,
CertificateKey: virtualServer.SSL.CertificatePath,
}
}

s.Locations = locs

return s, warnings
}

Expand Down
15 changes: 12 additions & 3 deletions internal/nginx/config/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@ type httpServers struct {
}

type server struct {
IsDefaultHTTP bool
IsDefaultSSL bool
ServerName string
SSL *ssl
ServerName string
Locations []location
IsDefaultHTTP bool
IsDefaultSSL bool
}

type location struct {
Return *returnVal
Path string
ProxyPass string
HTTPMatchVar string
Internal bool
}

type returnVal struct {
Code statusCode
}

type ssl struct {
Certificate string
CertificateKey string
}

type statusCode int

const statusNotFound statusCode = 404
7 changes: 5 additions & 2 deletions internal/nginx/config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,18 @@ server {
{{ if $l.Internal }}
internal;
{{ end }}

proxy_set_header Host $host;

{{ if $l.Return }}
return {{ $l.Return.Code }};
{{ end }}

{{ if $l.HTTPMatchVar }}
set $http_matches {{ $l.HTTPMatchVar | printf "%q" }};
js_content httpmatches.redirect;
{{ end }}

{{ if $l.ProxyPass }}
proxy_set_header Host $host;
proxy_pass {{ $l.ProxyPass }}$request_uri;
{{ end }}
}
Expand Down
37 changes: 35 additions & 2 deletions internal/state/change_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ var _ = Describe("ChangeProcessor", func() {
},
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}

Expand Down Expand Up @@ -333,6 +337,10 @@ var _ = Describe("ChangeProcessor", func() {
},
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
Expand Down Expand Up @@ -419,6 +427,10 @@ var _ = Describe("ChangeProcessor", func() {
},
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
Expand Down Expand Up @@ -505,6 +517,10 @@ var _ = Describe("ChangeProcessor", func() {
},
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
Expand Down Expand Up @@ -590,6 +606,10 @@ var _ = Describe("ChangeProcessor", func() {
CertificatePath: certificatePath,
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
Expand Down Expand Up @@ -669,6 +689,10 @@ var _ = Describe("ChangeProcessor", func() {
},
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
Expand Down Expand Up @@ -754,6 +778,10 @@ var _ = Describe("ChangeProcessor", func() {
},
},
},
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
Expand Down Expand Up @@ -791,12 +819,17 @@ var _ = Describe("ChangeProcessor", func() {
Expect(helpers.Diff(expectedStatuses, statuses)).To(BeEmpty())
})

It("should return empty configuration and updated statuses after deleting the second HTTPRoute", func() {
It("should return configuration with default ssl server and updated statuses after deleting the second HTTPRoute", func() {
processor.CaptureDeleteChange(&v1alpha2.HTTPRoute{}, types.NamespacedName{Namespace: "test", Name: "hr-2"})

expectedConf := state.Configuration{
HTTPServers: []state.VirtualServer{},
SSLServers: []state.VirtualServer{},
SSLServers: []state.VirtualServer{
{
Hostname: "~^",
SSL: &state.SSL{CertificatePath: certificatePath},
},
},
}
expectedStatuses := state.Statuses{
GatewayClassStatus: &state.GatewayClassStatus{
Expand Down
42 changes: 37 additions & 5 deletions internal/state/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sigs.k8s.io/gateway-api/apis/v1alpha2"
)

const wildcardHostname = "~^"

// Configuration is an internal representation of Gateway configuration.
// We can think of Configuration as an intermediate state between the Gateway API resources and the data plane (NGINX)
// configuration.
Expand Down Expand Up @@ -89,8 +91,8 @@ type configBuilder struct {

func newConfigBuilder() *configBuilder {
return &configBuilder{
http: newVirtualServerBuilder(),
ssl: newVirtualServerBuilder(),
http: newVirtualServerBuilder(v1alpha2.HTTPProtocolType),
ssl: newVirtualServerBuilder(v1alpha2.HTTPSProtocolType),
}
}

Expand All @@ -113,19 +115,27 @@ func (b *configBuilder) build() Configuration {
}

type virtualServerBuilder struct {
protocolType v1alpha2.ProtocolType
rulesPerHost map[string]map[string]PathRule
listenersForHost map[string]*listener
listeners []*listener
}

func newVirtualServerBuilder() *virtualServerBuilder {
func newVirtualServerBuilder(protocolType v1alpha2.ProtocolType) *virtualServerBuilder {
return &virtualServerBuilder{
protocolType: protocolType,
rulesPerHost: make(map[string]map[string]PathRule),
listenersForHost: make(map[string]*listener),
listeners: make([]*listener, 0),
}
}

func (b *virtualServerBuilder) upsertListener(l *listener) {

if b.protocolType == v1alpha2.HTTPSProtocolType {
b.listeners = append(b.listeners, l)
}

for _, r := range l.Routes {
var hostnames []string

Expand All @@ -137,13 +147,15 @@ func (b *virtualServerBuilder) upsertListener(l *listener) {

for _, h := range hostnames {
b.listenersForHost[h] = l

if _, exist := b.rulesPerHost[h]; !exist {
b.rulesPerHost[h] = make(map[string]PathRule)
}
}

for i, rule := range r.Source.Spec.Rules {
for _, h := range hostnames {

for j, m := range rule.Matches {
path := getPath(m.Path)

Expand All @@ -167,7 +179,7 @@ func (b *virtualServerBuilder) upsertListener(l *listener) {

func (b *virtualServerBuilder) build() []VirtualServer {

servers := make([]VirtualServer, 0, len(b.rulesPerHost))
servers := make([]VirtualServer, 0, len(b.rulesPerHost)+len(b.listeners))

for h, rules := range b.rulesPerHost {
s := VirtualServer{
Expand Down Expand Up @@ -198,14 +210,34 @@ func (b *virtualServerBuilder) build() []VirtualServer {
servers = append(servers, s)
}

// sort servers for predictable order
for _, l := range b.listeners {
hostname := getListenerHostname(l.Source.Hostname)
// generate a 404 ssl server block for listeners with no routes or listeners with wildcard (match-all) routes
// FIXME(kate-osborn): when we support regex hostnames (e.g. *.example.com) we will have to modify this check to catch regex hostnames.
if len(l.Routes) == 0 || hostname == wildcardHostname {
servers = append(servers, VirtualServer{
Hostname: hostname,
SSL: &SSL{CertificatePath: l.SecretPath},
})
}
}

sort.Slice(servers, func(i, j int) bool {
return servers[i].Hostname < servers[j].Hostname
})

return servers
}

func getListenerHostname(h *v1alpha2.Hostname) string {
name := getHostname(h)
if name == "" {
return wildcardHostname
}

return name
}

func getPath(path *v1alpha2.HTTPPathMatch) string {
if path == nil || path.Value == nil || *path.Value == "" {
return "/"
Expand Down
Loading