Skip to content

Commit a482e7e

Browse files
committed
Replace/remove password authentication with key based authentication
1 parent 065bd11 commit a482e7e

File tree

14 files changed

+198
-24
lines changed

14 files changed

+198
-24
lines changed

Package.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,20 @@ let package = Package(
2727
),
2828
.target(
2929
name: "SecureShell",
30-
dependencies: ["Clibssh"]
30+
dependencies: [
31+
.target(name: "Clibssh"),
32+
.product(name: "Path", package: "Path.swift"),
33+
]
34+
),
35+
.testTarget(
36+
name: "SecureShellTests",
37+
dependencies: [
38+
.target(name: "SecureShell"),
39+
.product(name: "Path", package: "Path.swift"),
40+
],
41+
resources: [
42+
.process("Fixtures"),
43+
]
3144
),
3245
.target(
3346
name: "VMwareFusion",

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,15 @@ and `cleanup_args` should be located in the respective help of each subcommand.
160160
prepare_args = [
161161
"prepare",
162162
"--ssh-username", "buildbot",
163-
"--ssh-key", "/Users/buildbot/Library/Application Support/me.lovelett.gitlab-fusion/id_ed25519",
163+
"--ssh-identity-file", "/Users/buildbot/Library/Application Support/me.lovelett.gitlab-fusion/id_ed25519",
164164
"/Users/buildbot/base-macOS-10.15.7-19H2-xcode-12.0.0.vmwarevm/base-macOS-10.15.7-19H2-xcode-12.0.0.vmx"
165165
]
166166

167167
run_exec = "/Users/buildbot/gitlab-fusion/.build/release/gitlab-fusion"
168168
run_args = [
169169
"run",
170170
"--ssh-username", "buildbot",
171-
"--ssh-key", "/Users/buildbot/Library/Application Support/me.lovelett.gitlab-fusion/id_ed25519",
171+
"--ssh-identity-file", "/Users/buildbot/Library/Application Support/me.lovelett.gitlab-fusion/id_ed25519",
172172
"/Users/buildbot/base-macOS-10.15.7-19H2-xcode-12.0.0.vmwarevm/base-macOS-10.15.7-19H2-xcode-12.0.0.vmx"
173173
]
174174

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// PrivateKey.swift
3+
// SecureShell
4+
//
5+
// Created by Ryan Lovelett on 10/5/20.
6+
//
7+
8+
import Clibssh
9+
import Path
10+
11+
/// Private key used with the SecureShell public-key infrastructure (PKI).
12+
final class PrivateKey: CustomStringConvertible {
13+
let key: ssh_key
14+
15+
/// Create an instance from a key at a path.
16+
/// - Parameter path: Path to the private key.
17+
/// - Throws: `SecureShellError` if the private key cannot be loaded.
18+
init(contentsOfFile path: Path) throws {
19+
var file: ssh_key?
20+
let returnCode = path.string.withCString { body in
21+
ssh_pki_import_privkey_file(body, nil, nil, nil, &file)
22+
}
23+
guard returnCode == SSH_OK, let privateKey = file else {
24+
ssh_key_free(file)
25+
throw SecureShellError("Could not import private key at \(path)")
26+
}
27+
self.key = privateKey
28+
}
29+
30+
deinit {
31+
ssh_key_free(key)
32+
}
33+
34+
/// The type of the key (e.g., `ssh-rsa` or `ssh-ed25519`).
35+
var description: String {
36+
let type = ssh_key_type(key)
37+
let name = ssh_key_type_to_char(type)
38+
return name.map { String(cString: $0) } ?? "unknown"
39+
}
40+
}

Sources/SecureShell/SecureShellError.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,12 @@ struct SecureShellError: LocalizedError {
4545
libsshErrorText = "Was not able to infer the error from \(session)."
4646
}
4747
}
48+
49+
var errorDescription: String? {
50+
if libsshErrorText.isEmpty {
51+
return description
52+
}
53+
54+
return "\(description) - \(libsshErrorText)"
55+
}
4856
}

Sources/SecureShell/Session.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Clibssh
9+
import Path
910

1011
/// A `Session` encapsulates the entire lifetime of the connection with a remote
1112
/// machine. A `Session` is responsible for connecting and authenticating the
@@ -52,17 +53,17 @@ public final class Session {
5253
ssh_free(session)
5354
}
5455

55-
/// Attempt to authenticate the session using password.
56+
/// Path to a file from which the identity (private key) for public key
57+
/// authentication is read.
5658
///
57-
/// - Parameter password: The password to attempt authentication with.
59+
/// - Parameter path: Path to identity private key.
5860
/// - Throws: `SecureShellError` if authentication fails.
59-
public func authenticate(withPassword password: String) throws {
60-
let returnCode = password.withCString {
61-
ssh_auth_e(rawValue: ssh_userauth_password(session, nil, $0))
62-
}
61+
public func authenticate(withIdentity path: Path) throws {
62+
let key = try PrivateKey(contentsOfFile: path)
63+
let returnCode = ssh_auth_e(rawValue: ssh_userauth_publickey(session, nil, key.key))
6364

6465
guard returnCode == SSH_AUTH_SUCCESS else {
65-
throw SecureShellError(session, description: "Could not authenticate.")
66+
throw SecureShellError(session, description: "Could not authenticate with identity \(path).")
6667
}
6768
}
6869

Sources/gitlab-fusion/Stages/Prepare.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,8 @@ struct Prepare: ParsableCommand {
5959

6060
// MARK: - Secure Shell (SSH) specific arguments
6161

62-
@Option(help: "User used to authenticate as over SSH to the VMware Fusion guest.")
63-
var sshUsername = "buildbot"
64-
65-
@Option(help: "Password used to authenticate as over SSH to the VMware Fusion guest.")
66-
var sshPassword = "Time2Build"
62+
@OptionGroup()
63+
var sshOptions: SecureShellOptions
6764

6865
// MARK: - Validating the command-line input
6966

@@ -133,8 +130,8 @@ struct Prepare: ParsableCommand {
133130
// Wait for ssh to become available
134131
for _ in 1...60 {
135132
// TODO: Retry if connection times out
136-
let session = try Session(host: ip, username: sshUsername)
137-
try session.authenticate(withPassword: sshPassword)
133+
let session = try Session(host: ip, username: sshOptions.sshUsername)
134+
try session.authenticate(withIdentity: sshOptions.sshIdentityFile)
138135
let channel = try session.openChannel()
139136
let exitCode = channel.execute("echo -n 2>&1")
140137

Sources/gitlab-fusion/Stages/Run.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,8 @@ struct Run: ParsableCommand {
4040

4141
// MARK: - Secure Shell (SSH) specific arguments
4242

43-
@Option(help: "User used to authenticate as over SSH to the VMware Fusion guest.")
44-
var sshUsername = "buildbot"
45-
46-
@Option(help: "Password used to authenticate as over SSH to the VMware Fusion guest.")
47-
var sshPassword = "Time2Build"
43+
@OptionGroup()
44+
var sshOptions: SecureShellOptions
4845

4946
// MARK: - Virtual Machine runtime specific arguments
5047

@@ -85,8 +82,8 @@ struct Run: ParsableCommand {
8582
let script = try String(contentsOf: scriptFile)
8683
os_log("Running script:\n%{public}@", log: log, type: .info, script)
8784

88-
let session = try Session(host: ip, username: sshUsername)
89-
try session.authenticate(withPassword: sshPassword)
85+
let session = try Session(host: ip, username: sshOptions.sshUsername)
86+
try session.authenticate(withIdentity: sshOptions.sshIdentityFile)
9087
let channel = try session.openChannel()
9188
let exitCode = channel.execute(script, stdout: FileHandle.standardOutput, stderr: FileHandle.standardError)
9289

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// SecureShellOptions.swift
3+
// gitlab-fusion
4+
//
5+
// Created by Ryan Lovelett on 10/10/20.
6+
//
7+
8+
import ArgumentParser
9+
import Environment
10+
import Path
11+
import VMwareFusion
12+
13+
/// Common arguments used by the individual stage subcommands.
14+
struct SecureShellOptions: ParsableArguments {
15+
@Option(help: "User used to authenticate as over SSH to the VMware Fusion guest.")
16+
var sshUsername = "buildbot"
17+
18+
@Option(help: "Path to a file from which the identity (private key) for public key authentication is read.")
19+
var sshIdentityFile = Path.applicationSupport / subsystem / "id_ed25519"
20+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN OPENSSH PRIVATE KEY-----
2+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
3+
QyNTUxOQAAACDuYamDhcrjukTnYHhKH9vR+4w786CBI+BTHiVi5Rx/uwAAAJianUMxmp1D
4+
MQAAAAtzc2gtZWQyNTUxOQAAACDuYamDhcrjukTnYHhKH9vR+4w786CBI+BTHiVi5Rx/uw
5+
AAAEDnc9ytgpASKJ+EjUy8fuw4GP6CpI9I/QFEQZnUmatjx+5hqYOFyuO6ROdgeEof29H7
6+
jDvzoIEj4FMeJWLlHH+7AAAAEFNlY3VyZVNoZWxsVGVzdHMBAgMEBQ==
7+
-----END OPENSSH PRIVATE KEY-----
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO5hqYOFyuO6ROdgeEof29H7jDvzoIEj4FMeJWLlHH+7 SecureShellTests

0 commit comments

Comments
 (0)