From e54f3dbc3a62b62bcbbad64ce01e1956f89d7cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20K=C3=A4stner?= Date: Tue, 2 Sep 2025 16:02:36 +0200 Subject: [PATCH] cisco-nxos-provider: handle existing trustpoint Trying to replace the configuration of an existing trustpoint will fail with "disassociating rsa key-pair not allowed when identity certificate exists". In order to avoid this, we check if the trustpoint already exists and return an error if it does. This can be checked in the caller and handled appropriately. Additionally, when deleting a trustpoint, it now also delete the associated keyring that is not automatically garbage collected by NX-OS. --- .../provider/cisco/nxos/crypto/trustpoint.go | 20 ++++++- .../cisco/nxos/crypto/trustpoint_test.go | 59 ++++++++++++++++--- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/internal/provider/cisco/nxos/crypto/trustpoint.go b/internal/provider/cisco/nxos/crypto/trustpoint.go index 2e83b8e2..8d813a80 100644 --- a/internal/provider/cisco/nxos/crypto/trustpoint.go +++ b/internal/provider/cisco/nxos/crypto/trustpoint.go @@ -4,6 +4,10 @@ package crypto import ( + "context" + "errors" + "fmt" + "github.com/openconfig/ygot/ygot" nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang" @@ -16,7 +20,18 @@ type Trustpoint struct { ID string } -func (t *Trustpoint) ToYGOT(_ gnmiext.Client) ([]gnmiext.Update, error) { +var ErrAlreadyExists = errors.New("crypto: trustpoint already exists") + +func (t *Trustpoint) ToYGOT(client gnmiext.Client) ([]gnmiext.Update, error) { + ctx := context.Background() + exists, err := client.Exists(ctx, "System/userext-items/pkiext-items/tp-items/TP-list[name="+t.ID+"]") + if err != nil { + return nil, fmt.Errorf("trustpoint: failed to get trustpoint %q: %w", t.ID, err) + } + if exists { + // Trying to replace an existing trustpoint configuration will fail with "disassociating rsa key-pair not allowed when identity certificate exists" + return nil, ErrAlreadyExists + } v := &nxos.Cisco_NX_OSDevice_System_UserextItems_PkiextItems_TpItems_TPList{} v.PopulateDefaults() v.Name = ygot.String(t.ID) @@ -33,5 +48,8 @@ func (t *Trustpoint) Reset(_ gnmiext.Client) ([]gnmiext.Update, error) { gnmiext.DeletingUpdate{ XPath: "System/userext-items/pkiext-items/tp-items/TP-list[name=" + t.ID + "]", }, + gnmiext.DeletingUpdate{ + XPath: "System/userext-items/pkiext-items/keyring-items/KeyRing-list[name=" + t.ID + "]", + }, }, nil } diff --git a/internal/provider/cisco/nxos/crypto/trustpoint_test.go b/internal/provider/cisco/nxos/crypto/trustpoint_test.go index 6a08d034..9e9bbefb 100644 --- a/internal/provider/cisco/nxos/crypto/trustpoint_test.go +++ b/internal/provider/cisco/nxos/crypto/trustpoint_test.go @@ -4,6 +4,7 @@ package crypto import ( + "context" "reflect" "testing" @@ -16,7 +17,18 @@ import ( func Test_Trustpoint(t *testing.T) { tp := &Trustpoint{ID: "mytrustpoint"} - got, err := tp.ToYGOT(&gnmiext.ClientMock{}) + // Mock the client to return false for Exists (trustpoint doesn't exist) + mockClient := &gnmiext.ClientMock{ + ExistsFunc: func(ctx context.Context, xpath string) (bool, error) { + expectedXPath := "System/userext-items/pkiext-items/tp-items/TP-list[name=mytrustpoint]" + if xpath != expectedXPath { + t.Errorf("Exists called with unexpected xpath: got=%s, want=%s", xpath, expectedXPath) + } + return false, nil + }, + } + + got, err := tp.ToYGOT(mockClient) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -36,16 +48,47 @@ func Test_Trustpoint(t *testing.T) { ti, ok := update.Value.(*nxos.Cisco_NX_OSDevice_System_UserextItems_PkiextItems_TpItems_TPList) if !ok { - t.Errorf("expected value to be of type *nxos.Cisco_NX_OSDevice_System_UserextItems_PkiextItems_TpItems") + t.Errorf("expected value to be of type *nxos.Cisco_NX_OSDevice_System_UserextItems_PkiextItems_TpItems_TPList") } - want := &nxos.Cisco_NX_OSDevice_System_UserextItems_PkiextItems_TpItems_TPList{ - Name: ygot.String("mytrustpoint"), - KeyType: nxos.Cisco_NX_OSDevice_Pki_KeyType_Type_RSA, - RevokeCheckConf: nxos.Cisco_NX_OSDevice_Pki_CertRevokeCheck_crl, - EnrollmentType: nxos.Cisco_NX_OSDevice_Pki_CertEnrollType_none, - } + // Create expected struct with PopulateDefaults to match the implementation + want := &nxos.Cisco_NX_OSDevice_System_UserextItems_PkiextItems_TpItems_TPList{} + want.PopulateDefaults() + want.Name = ygot.String("mytrustpoint") + if !reflect.DeepEqual(ti, want) { t.Errorf("unexpected value for 'System/userext-items/pkiext-items/tp-items/TP-list[name=mytrustpoint]': got=%+v, want=%+v", ti, want) } + + // Verify that Exists was called exactly once + existsCalls := mockClient.ExistsCalls() + if len(existsCalls) != 1 { + t.Errorf("expected Exists to be called once, got %d calls", len(existsCalls)) + } +} + +func Test_Trustpoint_AlreadyExists(t *testing.T) { + tp := &Trustpoint{ID: "mytrustpoint"} + + // Mock the client to return true for Exists (trustpoint already exists) + mockClient := &gnmiext.ClientMock{ + ExistsFunc: func(ctx context.Context, xpath string) (bool, error) { + expectedXPath := "System/userext-items/pkiext-items/tp-items/TP-list[name=mytrustpoint]" + if xpath != expectedXPath { + t.Errorf("Exists called with unexpected xpath: got=%s, want=%s", xpath, expectedXPath) + } + return true, nil + }, + } + + _, err := tp.ToYGOT(mockClient) + if err != ErrAlreadyExists { + t.Errorf("expected ErrAlreadyExists, got %v", err) + } + + // Verify that Exists was called exactly once + existsCalls := mockClient.ExistsCalls() + if len(existsCalls) != 1 { + t.Errorf("expected Exists to be called once, got %d calls", len(existsCalls)) + } }