forked from openconfig/featureprofiles
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Credentialz-2 "This code is a Contribution to the OpenConfig Feature Profiles project ("Work") made under the Google Software Grant and Corporate Contributor License Agreement ("CLA") and governed by the Apache License 2.0. No other rights or licenses in or to any of Nokia's intellectual property are granted for any other purpose. This code is provided on an "as is" basis without any warranties of any kind." * Fix telemetry check "This code is a Contribution to the OpenConfig Feature Profiles project ("Work") made under the Google Software Grant and Corporate Contributor License Agreement ("CLA") and governed by the Apache License 2.0. No other rights or licenses in or to any of Nokia's intellectual property are granted for any other purpose. This code is provided on an "as is" basis without any warranties of any kind." * Refactor Code "This code is a Contribution to the OpenConfig Feature Profiles project ("Work") made under the Google Software Grant and Corporate Contributor License Agreement ("CLA") and governed by the Apache License 2.0. No other rights or licenses in or to any of Nokia's intellectual property are granted for any other purpose. This code is provided on an "as is" basis without any warranties of any kind." * Credz library "This code is a Contribution to the OpenConfig Feature Profiles project ("Work") made under the Google Software Grant and Corporate Contributor License Agreement ("CLA") and governed by the Apache License 2.0. No other rights or licenses in or to any of Nokia's intellectual property are granted for any other purpose. This code is provided on an "as is" basis without any warranties of any kind."
- Loading branch information
1 parent
594214a
commit 6948980
Showing
6 changed files
with
1,319 additions
and
1,023 deletions.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Credentialz-2: SSH Password Login Disallowed | ||
|
||
## Summary | ||
|
||
Test that Credentialz properly disallows password based SSH authentication when configured to do | ||
so, furthermore, ensure that certificate based SSH authentication is allowed, and properly | ||
accounted for. | ||
|
||
|
||
## Procedure | ||
|
||
* Create a ssh CA keypair with `ssh-keygen -f /tmp/ca` | ||
* Create a user keypair with `ssh-keygen -t ed25519` | ||
* Sign the user public key into a certificate using the CA using `ssh-keygen -s | ||
/tmp/ca -I testuser -n principal_name -V +52w user.pub`. You will | ||
find your certificate ending in `-cert.pub` | ||
* Set DUT TrustedUserCAKeys using gnsi.Credentialz with the CA public key | ||
* Set a username of `testuser` with a password having following restrictions: | ||
* Must be 24-32 characters long. | ||
* Must use 4 of the 5 character classes ([a-z], [A-Z], [0-9], [!@#$%^&*(){}[]\|:;'"], [ ]). | ||
* Set DUT authentication types to permit only public key (PUBKEY) using gnsi.Credentialz | ||
* Set DUT authorized_users for `testuser` with a principal of `my_principal` (configured above | ||
when signing public key) | ||
* Perform the following tests and assert the expected result: | ||
* Case 1: Failure | ||
* Authenticate with the `testuser` username and password created above | ||
via SSH | ||
* Assert that authentication has failed | ||
* Ensure that access failure telemetry counters are incremented | ||
`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-rejects` | ||
`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-reject` | ||
* Case 2: Success | ||
* Authenticate with the `testuser` username and password created above | ||
via console | ||
* Assert that authentication has been successful (password authentication was only | ||
disallowed for SSH) | ||
* Ensure that access accept telemetry counters are incremented | ||
`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` | ||
`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` | ||
* Case 3: Success | ||
* Authenticate with the `testuser` and certificate created above | ||
* Assert that authentication has been successful | ||
* Assert that gnsi accounting recorded the principal (`my_principal`) from the | ||
certificate rather than the SSH username (`testuser`) | ||
* Ensure that access accept telemetry counters are incremented | ||
`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` | ||
`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` | ||
|
||
|
||
## OpenConfig Path and RPC Coverage | ||
|
||
The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. | ||
|
||
```yaml | ||
paths: | ||
## State Paths ## | ||
/system/ssh-server/state/counters/access-rejects: | ||
/system/ssh-server/state/counters/last-access-reject: | ||
/system/ssh-server/state/counters/access-accepts: | ||
/system/ssh-server/state/counters/last-access-accept: | ||
|
||
rpcs: | ||
gnsi: | ||
credentialz.v1.Credentialz.RotateAccountCredentials: | ||
credentialz.v1.Credentialz.RotateHostParameters: | ||
``` | ||
## Minimum DUT platform requirement | ||
N/A |
16 changes: 16 additions & 0 deletions
16
feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto | ||
# proto-message: Metadata | ||
|
||
uuid: "4632f134-71e6-46a2-ac2a-b1ff6a3444e6" | ||
plan_id: "Credentialz-2" | ||
description: "SSH Password Login Disallowed" | ||
testbed: TESTBED_DUT | ||
|
||
platform_exceptions: { | ||
platform: { | ||
vendor: NOKIA | ||
} | ||
deviations: { | ||
ssh_server_counters_unsupported: true | ||
} | ||
} |
188 changes: 188 additions & 0 deletions
188
...nsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// 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 sshpasswordlogindisallowed | ||
|
||
import ( | ||
"context" | ||
"os" | ||
|
||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
|
||
"testing" | ||
"time" | ||
|
||
"google.golang.org/protobuf/types/known/timestamppb" | ||
|
||
"github.com/openconfig/featureprofiles/internal/deviations" | ||
"github.com/openconfig/featureprofiles/internal/fptest" | ||
"github.com/openconfig/featureprofiles/internal/security/credz" | ||
acctzpb "github.com/openconfig/gnsi/acctz" | ||
cpb "github.com/openconfig/gnsi/credentialz" | ||
"github.com/openconfig/ondatra" | ||
) | ||
|
||
const ( | ||
username = "testuser" | ||
userPrincipal = "my_principal" | ||
command = "show version" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
fptest.RunTests(m) | ||
} | ||
|
||
func TestCredentialz(t *testing.T) { | ||
dut := ondatra.DUT(t, "dut") | ||
target := credz.GetDutTarget(t, dut) | ||
recordStartTime := timestamppb.New(time.Now()) | ||
|
||
// Create temporary directory for storing ssh keys/certificates. | ||
dir, err := os.MkdirTemp("", "") | ||
if err != nil { | ||
t.Fatalf("Creating temp dir, err: %s", err) | ||
} | ||
defer func(dir string) { | ||
err = os.RemoveAll(dir) | ||
if err != nil { | ||
t.Logf("Error removing temp directory, error: %s", err) | ||
} | ||
}(dir) | ||
|
||
// Create ssh keys/certificates for CA & testuser. | ||
credz.CreateSSHKeyPair(t, dir, "ca") | ||
credz.CreateSSHKeyPair(t, dir, username) | ||
credz.CreateUserCertificate(t, dir, userPrincipal) | ||
|
||
// Setup user and password. | ||
credz.SetupUser(t, dut, username) | ||
password := credz.GeneratePassword() | ||
credz.RotateUserPassword(t, dut, username, password, "v1.0", uint64(time.Now().Unix())) | ||
|
||
credz.RotateTrustedUserCA(t, dut, dir) | ||
credz.RotateAuthenticationTypes(t, dut, []cpb.AuthenticationType{ | ||
cpb.AuthenticationType_AUTHENTICATION_TYPE_PUBKEY, | ||
}) | ||
credz.RotateAuthorizedPrincipal(t, dut, username, userPrincipal) | ||
|
||
t.Run("auth should fail ssh password authentication disallowed", func(t *testing.T) { | ||
var startingRejectCounter, startingLastRejectTime uint64 | ||
if !deviations.SSHServerCountersUnsupported(dut) { | ||
startingRejectCounter, startingLastRejectTime = credz.GetRejectTelemetry(t, dut) | ||
} | ||
|
||
// Verify ssh with password fails as expected. | ||
_, err := credz.SSHWithPassword(target, username, password) | ||
if err == nil { | ||
t.Fatalf("Dialing ssh succeeded, but we expected to fail.") | ||
} | ||
|
||
// Verify ssh counters. | ||
if !deviations.SSHServerCountersUnsupported(dut) { | ||
endingRejectCounter, endingLastRejectTime := credz.GetRejectTelemetry(t, dut) | ||
if endingRejectCounter <= startingRejectCounter { | ||
t.Fatalf("SSH server reject counter did not increment after unsuccessful login. startCounter: %v, endCounter: %v", startingRejectCounter, endingRejectCounter) | ||
} | ||
if startingLastRejectTime == endingLastRejectTime { | ||
t.Fatalf("SSH server reject last timestamp did not update after unsuccessful login. Timestamp: %v", endingLastRejectTime) | ||
} | ||
} | ||
}) | ||
|
||
t.Run("auth should succeed ssh certificate authentication allowed", func(t *testing.T) { | ||
var startingAcceptCounter, startingLastAcceptTime uint64 | ||
if !deviations.SSHServerCountersUnsupported(dut) { | ||
startingAcceptCounter, startingLastAcceptTime = credz.GetAcceptTelemetry(t, dut) | ||
} | ||
|
||
// Verify ssh with certificate succeeds. | ||
conn, err := credz.SSHWithCertificate(t, target, username, dir) | ||
if err != nil { | ||
t.Fatalf("Dialing ssh failed, but we expected to succeed, error: %s", err) | ||
} | ||
defer conn.Close() | ||
|
||
// Send command for accounting. | ||
sess, err := conn.NewSession() | ||
if err != nil { | ||
t.Fatalf("Failed creating ssh session, error: %s", err) | ||
} | ||
defer sess.Close() | ||
sess.Run(command) | ||
|
||
// Verify ssh counters. | ||
if !deviations.SSHServerCountersUnsupported(dut) { | ||
endingAcceptCounter, endingLastAcceptTime := credz.GetAcceptTelemetry(t, dut) | ||
if endingAcceptCounter <= startingAcceptCounter { | ||
t.Fatalf("SSH server accept counter did not increment after successful login. startCounter: %v, endCounter: %v", startingAcceptCounter, endingAcceptCounter) | ||
} | ||
if startingLastAcceptTime == endingLastAcceptTime { | ||
t.Fatalf("SSH server accept last timestamp did not update after successful login. Timestamp: %v", endingLastAcceptTime) | ||
} | ||
} | ||
|
||
// Verify accounting record. | ||
acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() | ||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
defer cancel() | ||
acctzSubClient, err := acctzClient.RecordSubscribe(ctx, &acctzpb.RecordRequest{Timestamp: recordStartTime}) | ||
if err != nil { | ||
t.Fatalf("Failed sending accountz record request, error: %s", err) | ||
} | ||
defer acctzSubClient.CloseSend() | ||
|
||
var foundRecord bool | ||
for { | ||
acctzResponse, err := acctzSubClient.Recv() | ||
if err != nil { | ||
if status.Code(err) == codes.DeadlineExceeded { | ||
t.Log("Done receiving records...") | ||
break | ||
} | ||
t.Fatalf( | ||
"Failed receiving from accountz record subscribe client, "+ | ||
"this could mean we didn't find the user identity and eventually timed "+ | ||
"out with no more records to review, or another server error. Error: %s", | ||
err, | ||
) | ||
} | ||
|
||
// Skip non-ssh records. | ||
if acctzResponse.GetCmdService() == nil { | ||
continue | ||
} | ||
|
||
reportedIdentity := acctzResponse.GetSessionInfo().GetUser().GetIdentity() | ||
if reportedIdentity == username { | ||
t.Logf("Found Record: %s", credz.PrettyPrint(acctzResponse)) | ||
foundRecord = true | ||
break | ||
} | ||
} | ||
if !foundRecord { | ||
t.Fatalf("Did not find accounting record for %s", username) | ||
} | ||
}) | ||
|
||
t.Cleanup(func() { | ||
// Cleanup to remove previous policy which only allowed key auth to make sure we don't | ||
// leave dut in a state where we can't reset config for further tests. | ||
credz.RotateAuthenticationTypes(t, dut, []cpb.AuthenticationType{ | ||
cpb.AuthenticationType_AUTHENTICATION_TYPE_PASSWORD, | ||
cpb.AuthenticationType_AUTHENTICATION_TYPE_PUBKEY, | ||
cpb.AuthenticationType_AUTHENTICATION_TYPE_KBDINTERACTIVE, | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.