Skip to content
Draft
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
27 changes: 26 additions & 1 deletion api/v1alpha/httpproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ type HTTPProxySpec struct {
// HTTPProxy. In such cases, these will be listed in the `status.hostnames`
// field and do not require additional configuration by the user.
//
// Wildcard hostnames are not supported at this time.
// A hostname may start with a single wildcard label, as in
// `*.s3.example.com`, where the platform offers wildcards. A wildcard
// matches names one or more labels beneath its base, its certificate
// covers names exactly one label beneath, and it needs its base or a parent
// verified by DNS TXT record. A wildcard reserves every name beneath it for
// its project.
//
// +kubebuilder:validation:Optional
// +kubebuilder:validation:MaxItems=16
Expand Down Expand Up @@ -633,6 +638,26 @@ const (
HostnameConditionCertificateReady = "CertificateReady"
)

// Reasons for HostnameConditionVerified.
const (
// HostnameVerifiedReasonVerified indicates a verified Domain covers the
// hostname.
HostnameVerifiedReasonVerified = "Verified"

// HostnameVerifiedReasonPendingVerification indicates no verified Domain
// covers the hostname yet.
HostnameVerifiedReasonPendingVerification = "PendingVerification"

// HostnameVerifiedReasonDNSVerificationRequired indicates a wildcard
// hostname whose base, or a parent of it, has not been verified by DNS TXT
// record. Other proofs do not cover every name beneath a wildcard.
HostnameVerifiedReasonDNSVerificationRequired = "DNSVerificationRequired"

// HostnameVerifiedReasonWildcardNotSupported indicates a wildcard hostname
// on a platform that does not offer wildcards.
HostnameVerifiedReasonWildcardNotSupported = "WildcardNotSupported"
)

// Reasons for HostnameConditionCertificateReady.
const (
// CertificateReadyReasonCertificateIssued indicates the certificate has been issued and is ready.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ spec:
HTTPProxy. In such cases, these will be listed in the `status.hostnames`
field and do not require additional configuration by the user.

Wildcard hostnames are not supported at this time.
A hostname may start with a single wildcard label, as in
`*.s3.example.com`, where the platform offers wildcards. A wildcard
matches names one or more labels beneath its base, its certificate
covers names exactly one label beneath, and it needs its base or a parent
verified by DNS TXT record. A wildcard reserves every name beneath it for
its project.
items:
description: |-
Hostname is the fully qualified domain name of a network host. This matches
Expand Down
7 changes: 6 additions & 1 deletion docs/api/httpproxies.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ The system may automatically generate and associate hostnames with the
HTTPProxy. In such cases, these will be listed in the `status.hostnames`
field and do not require additional configuration by the user.

Wildcard hostnames are not supported at this time.<br/>
A hostname may start with a single wildcard label, as in
`*.s3.example.com`, where the platform offers wildcards. A wildcard
matches names one or more labels beneath its base, its certificate
covers names exactly one label beneath, and it needs its base or a parent
verified by DNS TXT record. A wildcard reserves every name beneath it for
its project.<br/>
</td>
<td>false</td>
</tr><tr>
Expand Down
20 changes: 20 additions & 0 deletions internal/agent/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,26 @@ var hostnameCatalog = []ReasonInfo{
"yours, raise it with Datum rather than searching for it.",
Skill: SkillHostnameNotWorking,
},
{
Reason: networkingv1alpha.HostnameVerifiedReasonDNSVerificationRequired,
ConditionType: networkingv1alpha.HostnameConditionVerified,
Actionability: ActionabilityUser,
Scope: ScopeOneHostname,
Explanation: "This wildcard hostname needs proof, by a DNS TXT record, that you control the domain " +
"beneath it. Proof over HTTP or through a Datum DNS zone does not cover every name a wildcard serves.",
Remediation: "Publish the TXT record the status message names, on the Domain it names. " +
"Once that Domain is verified by DNS, the wildcard is admitted on its own.",
Skill: SkillDomainVerification,
},
{
Reason: networkingv1alpha.HostnameVerifiedReasonWildcardNotSupported,
ConditionType: networkingv1alpha.HostnameConditionVerified,
Actionability: ActionabilityUser,
Scope: ScopeOneHostname,
Explanation: "Wildcard hostnames are not available on this platform, so this hostname will not serve.",
Remediation: "Replace the wildcard with the exact hostnames you need.",
Skill: SkillHostnameNotWorking,
},
{
Reason: networkingv1alpha.CertificatesReadyReasonAllCertificatesReady,
ConditionType: networkingv1alpha.HTTPProxyConditionCertificatesReady,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/alb/spec/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func toHostnames(hostnames []string) []gatewayv1.Hostname {
}

func validateProxy(proxy *networkingv1alpha.HTTPProxy) error {
errs := validation.ValidateHTTPProxy(proxy)
errs := validation.ValidateHTTPProxy(proxy, validation.HTTPProxyValidationOptions{})
if len(errs) == 0 {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ func webhookRegistrations(mgr mcmanager.Manager, serverConfig config.NetworkServ
return networkinggatewayv1webhooks.SetupBackendTLSPolicyWebhookWithManager(mgr)
}},
{"HTTPProxy", true, func() error {
return networkingv1alphawebhooks.SetupHTTPProxyWebhookWithManager(mgr)
return networkingv1alphawebhooks.SetupHTTPProxyWebhookWithManager(mgr, serverConfig.Gateway)
}},
{"TrafficProtectionPolicy", true, func() error {
return networkingv1alphawebhooks.SetupTrafficProtectionPolicyWebhookWithManager(mgr)
Expand Down
16 changes: 16 additions & 0 deletions internal/controller/domain_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ func (r *DomainReconciler) reconcileVerification(ctx context.Context, reader cli
apimeta.RemoveStatusCondition(&domainStatus.Conditions, networkingv1alpha.DomainConditionVerifiedDNS)
apimeta.RemoveStatusCondition(&domainStatus.Conditions, networkingv1alpha.DomainConditionVerifiedHTTP)
apimeta.RemoveStatusCondition(&domainStatus.Conditions, networkingv1alpha.DomainConditionVerifiedDNSZone)
if r.Config.Gateway.CertificateService.Enabled {
recordVerificationMethod(domainStatus, verifiedDNSCondition, verifiedHTTPCondition)
}
// When verified, no future verification timer is needed
nextAttempt = time.Time{}
}
Expand All @@ -335,6 +338,19 @@ func (r *DomainReconciler) reconcileVerification(ctx context.Context, reader cli
return nextAttempt
}

// recordVerificationMethod keeps the condition for the method that proved
// ownership, so a consumer can tell DNS proof from HTTP proof after the
// verification scaffolding is cleared. DNS wins when both passed.
func recordVerificationMethod(domainStatus *networkingv1alpha.DomainStatus, verifiedDNS, verifiedHTTP *metav1.Condition) {
if verifiedDNS.Status == metav1.ConditionTrue {
apimeta.SetStatusCondition(&domainStatus.Conditions, *verifiedDNS)
return
}
if verifiedHTTP.Status == metav1.ConditionTrue {
apimeta.SetStatusCondition(&domainStatus.Conditions, *verifiedHTTP)
}
}

var dnsZoneListGVK = schema.GroupVersionKind{
Group: "dns.networking.miloapis.com",
Version: versionV1Alpha1,
Expand Down
46 changes: 45 additions & 1 deletion internal/controller/domain_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,50 @@ func TestDomainVerification(t *testing.T) {
reconcileCount int
// registryLookupDomain allows a test to control Domain.status.nameservers via reconcileRegistration.
registryLookupDomain func(ctx context.Context, domain string, opts registrydata.LookupOptions) (*registrydata.DomainResult, error)
certificateService bool
assert func(t *testing.T, domain *networkingv1alpha.Domain, result ctrl.Result)
}{
{
name: "dns record verification is remembered with the certificate service on",
certificateService: true,
lookupTXT: func(ctx context.Context, name string) ([]string, error) {
return []string{"test"}, nil
},
httpGet: func(ctx context.Context, url string) ([]byte, *http.Response, error) {
return []byte("test"), &http.Response{StatusCode: http.StatusOK}, nil
},
domain: newDomain(upstreamNamespace.Name, "dns-verify-remembered", func(domain *networkingv1alpha.Domain) {
domain.Status.Verification = &networkingv1alpha.DomainVerificationStatus{
DNSRecord: networkingv1alpha.DNSVerificationRecord{Name: "test", Type: "TXT", Content: "test"},
HTTPToken: networkingv1alpha.HTTPVerificationToken{URL: "test", Body: "test"},
}
}),
assert: func(t *testing.T, domain *networkingv1alpha.Domain, result ctrl.Result) {
assert.True(t, apimeta.IsStatusConditionTrue(domain.Status.Conditions, networkingv1alpha.DomainConditionVerified))
assert.True(t, apimeta.IsStatusConditionTrue(domain.Status.Conditions, networkingv1alpha.DomainConditionVerifiedDNS))
assert.Nil(t, apimeta.FindStatusCondition(domain.Status.Conditions, networkingv1alpha.DomainConditionVerifiedHTTP))
assert.Nil(t, domain.Status.Verification, "a verified domain must not show its verification scaffolding")
},
},
{
name: "http token verification is remembered with the certificate service on",
certificateService: true,
lookupTXT: func(ctx context.Context, name string) ([]string, error) {
return []string{}, &net.DNSError{IsNotFound: true}
},
httpGet: func(ctx context.Context, url string) ([]byte, *http.Response, error) {
return []byte("test"), &http.Response{StatusCode: http.StatusOK}, nil
},
domain: newDomain(upstreamNamespace.Name, "http-verify-remembered", func(domain *networkingv1alpha.Domain) {
domain.Status.Verification = &networkingv1alpha.DomainVerificationStatus{
HTTPToken: networkingv1alpha.HTTPVerificationToken{URL: "test", Body: "test"},
}
}),
assert: func(t *testing.T, domain *networkingv1alpha.Domain, result ctrl.Result) {
assert.True(t, apimeta.IsStatusConditionTrue(domain.Status.Conditions, networkingv1alpha.DomainConditionVerifiedHTTP))
assert.Nil(t, apimeta.FindStatusCondition(domain.Status.Conditions, networkingv1alpha.DomainConditionVerifiedDNS))
},
},
{
name: "verification details added to status",
domain: newDomain(upstreamNamespace.Name, "test"),
Expand Down Expand Up @@ -541,9 +583,11 @@ func TestDomainVerification(t *testing.T) {

mgr := &fakeMockManager{cl: fakeUpstreamClient}

reconcilerConfig := operatorConfig
reconcilerConfig.Gateway.CertificateService.Enabled = tt.certificateService
reconciler := &DomainReconciler{
mgr: mgr,
Config: operatorConfig,
Config: reconcilerConfig,

timeNow: tt.timeNow,
httpGet: tt.httpGet,
Expand Down
Loading
Loading