diff --git a/.changelog/17115.txt b/.changelog/17115.txt new file mode 100644 index 00000000000..8b6a9090db9 --- /dev/null +++ b/.changelog/17115.txt @@ -0,0 +1,3 @@ +```release-note:improvement +gateway: Change status condition reason for invalid certificate on a listener from "Accepted" to "ResolvedRefs". +``` diff --git a/agent/consul/gateways/controller_gateways.go b/agent/consul/gateways/controller_gateways.go index 2cfa462a947..41c6b2b9fd1 100644 --- a/agent/consul/gateways/controller_gateways.go +++ b/agent/consul/gateways/controller_gateways.go @@ -240,6 +240,19 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle return err } + // set each listener as having valid certs, then overwrite that status condition + // if there are any certificate errors + meta.eachListener(func(listener *structs.APIGatewayListener, bound *structs.BoundAPIGatewayListener) error { + listenerRef := structs.ResourceReference{ + Kind: structs.APIGateway, + Name: meta.BoundGateway.Name, + SectionName: bound.Name, + EnterpriseMeta: meta.BoundGateway.EnterpriseMeta, + } + updater.SetCondition(conditions.validCertificate(listenerRef)) + return nil + }) + for ref, err := range certificateErrors { updater.SetCondition(conditions.invalidCertificate(ref, err)) } @@ -744,8 +757,14 @@ func (g *gatewayMeta) checkCertificates(store *state.Store) (map[structs.Resourc if err != nil { return err } + listenerRef := structs.ResourceReference{ + Kind: structs.APIGateway, + Name: g.BoundGateway.Name, + SectionName: bound.Name, + EnterpriseMeta: g.BoundGateway.EnterpriseMeta, + } if certificate == nil { - certificateErrors[ref] = errors.New("certificate not found") + certificateErrors[listenerRef] = fmt.Errorf("certificate %q not found", ref.Name) } else { bound.Certificates = append(bound.Certificates, ref) } @@ -855,7 +874,7 @@ func newGatewayConditionGenerator() *gatewayConditionGenerator { // to a given APIGateway listener. func (g *gatewayConditionGenerator) invalidCertificate(ref structs.ResourceReference, err error) structs.Condition { return structs.Condition{ - Type: "Accepted", + Type: "ResolvedRefs", Status: "False", Reason: "InvalidCertificate", Message: err.Error(), @@ -864,6 +883,17 @@ func (g *gatewayConditionGenerator) invalidCertificate(ref structs.ResourceRefer } } +func (g *gatewayConditionGenerator) validCertificate(ref structs.ResourceReference) structs.Condition { + return structs.Condition{ + Type: "ResolvedRefs", + Status: "True", + Reason: "ResolvedRefs", + Message: "resolved refs", + Resource: pointerTo(ref), + LastTransitionTime: g.now, + } +} + // invalidCertificates is used to set the overall condition of the APIGateway // to invalid due to missing certificates that it references. func (g *gatewayConditionGenerator) invalidCertificates() structs.Condition { diff --git a/agent/consul/gateways/controller_gateways_test.go b/agent/consul/gateways/controller_gateways_test.go index 805a5738ce6..a0389efce7b 100644 --- a/agent/consul/gateways/controller_gateways_test.go +++ b/agent/consul/gateways/controller_gateways_test.go @@ -2009,6 +2009,12 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, SectionName: "listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + SectionName: "listener", + }), }, }, }, @@ -2101,6 +2107,12 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, SectionName: "listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + SectionName: "listener", + }), }, }, }, @@ -2224,6 +2236,12 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, SectionName: "listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + SectionName: "listener", + }), }, }, }, @@ -2367,6 +2385,12 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, SectionName: "listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + SectionName: "listener", + }), }, }, }, @@ -2508,6 +2532,12 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, SectionName: "listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + SectionName: "listener", + }), }, }, }, @@ -2666,6 +2696,16 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", SectionName: "tcp-listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "http-listener", + }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "tcp-listener", + }), }, }, }, @@ -3010,6 +3050,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", SectionName: "http-listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "http-listener", + }), }, }, }, @@ -3284,9 +3329,10 @@ func TestAPIGatewayController(t *testing.T) { Status: structs.Status{ Conditions: []structs.Condition{ conditions.invalidCertificate(structs.ResourceReference{ - Kind: structs.InlineCertificate, - Name: "certificate", - }, errors.New("certificate not found")), + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "http-listener", + }, errors.New("certificate \"certificate\" not found")), conditions.invalidCertificates(), conditions.gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, @@ -3357,6 +3403,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", SectionName: "http-listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "http-listener", + }), }, }, }, @@ -3375,6 +3426,345 @@ func TestAPIGatewayController(t *testing.T) { }, }, }, + "all-listeners-valid-certificate-refs": { + requests: []controller.Request{{ + Kind: structs.APIGateway, + Name: "gateway", + Meta: acl.DefaultEnterpriseMeta(), + }}, + initialEntries: []structs.ConfigEntry{ + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{ + { + Name: "listener-1", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "cert-1", + }}, + }, + }, + { + Name: "listener-2", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "cert-2", + }}, + }, + }, + }, + }, + &structs.InlineCertificateConfigEntry{ + Kind: structs.InlineCertificate, + Name: "cert-1", + EnterpriseMeta: *defaultMeta, + }, + &structs.InlineCertificateConfigEntry{ + Kind: structs.InlineCertificate, + Name: "cert-2", + EnterpriseMeta: *defaultMeta, + }, + }, + finalEntries: []structs.ConfigEntry{ + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{ + { + Name: "listener-1", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "cert-1", + }}, + }, + }, + { + Name: "listener-2", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "cert-2", + }}, + }, + }, + }, + Status: structs.Status{ + Conditions: []structs.Condition{ + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-1", + }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-2", + }), + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-1", + }), + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-2", + }), + conditions.gatewayAccepted(), + }, + }, + }, + &structs.BoundAPIGatewayConfigEntry{ + Kind: structs.BoundAPIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.BoundAPIGatewayListener{ + { + Name: "listener-1", + Certificates: []structs.ResourceReference{ + { + Kind: structs.InlineCertificate, + Name: "cert-1", + }, + }, + }, + { + Name: "listener-2", + Certificates: []structs.ResourceReference{ + { + Kind: structs.InlineCertificate, + Name: "cert-2", + }, + }, + }, + }, + }, + }, + }, + "all-listeners-invalid-certificates": { + requests: []controller.Request{{ + Kind: structs.APIGateway, + Name: "gateway", + Meta: acl.DefaultEnterpriseMeta(), + }}, + initialEntries: []structs.ConfigEntry{ + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{ + { + Name: "listener-1", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "missing certificate", + }}, + }, + }, + { + Name: "listener-2", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "another missing certificate", + }}, + }, + }, + }, + }, + }, + finalEntries: []structs.ConfigEntry{ + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{ + { + Name: "listener-1", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "missing certificate", + }}, + }, + }, + { + Name: "listener-2", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "another missing certificate", + }}, + }, + }, + }, + Status: structs.Status{ + Conditions: []structs.Condition{ + conditions.invalidCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-1", + }, errors.New("certificate \"missing certificate\" not found")), + conditions.invalidCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-2", + }, errors.New("certificate \"another missing certificate\" not found")), + conditions.invalidCertificates(), + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-1", + }), + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "listener-2", + }), + }, + }, + }, + &structs.BoundAPIGatewayConfigEntry{ + Kind: structs.BoundAPIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.BoundAPIGatewayListener{ + {Name: "listener-1"}, + {Name: "listener-2"}, + }, + }, + }, + }, + "mixed-valid-and-invalid-certificate-refs-for-listeners": { + requests: []controller.Request{{ + Kind: structs.APIGateway, + Name: "gateway", + Meta: acl.DefaultEnterpriseMeta(), + }}, + initialEntries: []structs.ConfigEntry{ + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{ + { + Name: "invalid-listener", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "missing certificate", + }}, + }, + }, + { + Name: "valid-listener", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "certificate", + }}, + }, + }, + }, + }, + &structs.InlineCertificateConfigEntry{ + Kind: structs.InlineCertificate, + Name: "certificate", + EnterpriseMeta: *defaultMeta, + }, + }, + finalEntries: []structs.ConfigEntry{ + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{ + { + Name: "invalid-listener", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "missing certificate", + }}, + }, + }, + { + Name: "valid-listener", + Port: 80, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "certificate", + }}, + }, + }, + }, + Status: structs.Status{ + Conditions: []structs.Condition{ + conditions.invalidCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "invalid-listener", + }, errors.New("certificate \"missing certificate\" not found")), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "valid-listener", + }), + + conditions.invalidCertificates(), + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "valid-listener", + }), + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "invalid-listener", + }), + }, + }, + }, + &structs.BoundAPIGatewayConfigEntry{ + Kind: structs.BoundAPIGateway, + Name: "gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.BoundAPIGatewayListener{ + { + Name: "valid-listener", + Certificates: []structs.ResourceReference{ + { + Kind: structs.InlineCertificate, + Name: "certificate", + }, + }, + }, + { + Name: "invalid-listener", + }, + }, + }, + }, + }, "updated-gateway-certificates": { requests: []controller.Request{{ Kind: structs.APIGateway, @@ -3440,6 +3830,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", SectionName: "http-listener", }), + conditions.validCertificate(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "gateway", + SectionName: "http-listener", + }), }, }, }, @@ -3621,7 +4016,7 @@ func TestAPIGatewayController(t *testing.T) { require.NoError(t, err) ppExpected, err := json.MarshalIndent(expectedStatus, "", " ") require.NoError(t, err) - require.True(t, statusEqual, fmt.Sprintf("statuses are unequal: %+v != %+v", string(ppActual), string(ppExpected))) + require.True(t, statusEqual, fmt.Sprintf("statuses are unequal (actual != expected): %+v != %+v", string(ppActual), string(ppExpected))) if bound, ok := controlled.(*structs.BoundAPIGatewayConfigEntry); ok { ppActual, err := json.MarshalIndent(bound, "", " ") require.NoError(t, err)