Skip to content

Commit e71d1bd

Browse files
Add systemd validation check to ValidateInstanceImage
- Add Check 4 to verify systemd is running or degraded - Update SECURITY.md to document systemd requirement - Update README.md Platform Support section - Refactor ValidateInstanceImage into smaller helper functions to fix lint - Validation tests automatically include new check Co-Authored-By: Alec Fong <alecsanf@usc.edu>
1 parent d5451ae commit e71d1bd

3 files changed

Lines changed: 81 additions & 40 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ See [SECURITY.md](docs/SECURITY.md) for complete security specifications and imp
5353
- **Operating System**: Currently supports Ubuntu 22 only
5454
- **Architecture**: Designed for GPU-accelerated compute workloads
5555
- **Access Method**: Requires SSH server and SSH key-based authentication
56+
- **System Requirements**: Requires systemd to be running and accessible
5657

5758
---
5859

docs/SECURITY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This document outlines the security requirements and best practices for implemen
1616
**Implementation Requirements:**
1717

1818
- SSH server (OpenSSH or equivalent) must be installed and running on all instances
19+
- systemd must be running and accessible via systemctl command
1920
- SSH key pairs must be supported for authentication
2021
- Public keys must be injectable during instance provisioning
2122
- SSH access must be available through the configured firewall rules
@@ -136,4 +137,4 @@ For security issues, vulnerabilities, or questions:
136137

137138
---
138139

139-
**Note**: This document is a living document and will be updated as security requirements evolve. All cloud integrations must comply with these requirements to ensure the security and integrity of the Brev Compute SDK ecosystem.
140+
**Note**: This document is a living document and will be updated as security requirements evolve. All cloud integrations must comply with these requirements to ensure the security and integrity of the Brev Compute SDK ecosystem.

pkg/v1/image.go

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,84 +30,123 @@ type Image struct {
3030
}
3131

3232
func ValidateInstanceImage(ctx context.Context, instance Instance, privateKey string) error {
33-
// First ensure the instance is running and SSH accessible
34-
sshUser := instance.SSHUser
35-
sshPort := instance.SSHPort
36-
publicIP := instance.PublicIP
33+
sshClient, err := connectToInstance(ctx, instance, privateKey)
34+
if err != nil {
35+
return err
36+
}
37+
defer func() {
38+
if closeErr := sshClient.Close(); closeErr != nil {
39+
fmt.Printf("warning: failed to close SSH connection: %v\n", closeErr)
40+
}
41+
}()
42+
43+
arch, err := validateArchitecture(ctx, sshClient)
44+
if err != nil {
45+
return err
46+
}
47+
48+
osVersion, err := validateOSVersion(ctx, sshClient)
49+
if err != nil {
50+
return err
51+
}
52+
53+
homeDir, err := validateHomeDirectory(ctx, sshClient, instance.SSHUser)
54+
if err != nil {
55+
return err
56+
}
57+
58+
systemdStatus, err := validateSystemd(ctx, sshClient)
59+
if err != nil {
60+
return err
61+
}
62+
63+
fmt.Printf("Instance image validation passed for %s: architecture=%s, os=%s, home=%s, systemd=%s\n",
64+
instance.CloudID, arch, osVersion, homeDir, systemdStatus)
65+
66+
return nil
67+
}
3768

38-
// Validate that we have the required SSH connection details
39-
if sshUser == "" {
40-
return fmt.Errorf("SSH user is not set for instance %s", instance.CloudID)
69+
func connectToInstance(ctx context.Context, instance Instance, privateKey string) (*ssh.Client, error) {
70+
if instance.SSHUser == "" {
71+
return nil, fmt.Errorf("SSH user is not set for instance %s", instance.CloudID)
4172
}
42-
if sshPort == 0 {
43-
return fmt.Errorf("SSH port is not set for instance %s", instance.CloudID)
73+
if instance.SSHPort == 0 {
74+
return nil, fmt.Errorf("SSH port is not set for instance %s", instance.CloudID)
4475
}
45-
if publicIP == "" {
46-
return fmt.Errorf("public IP is not available for instance %s", instance.CloudID)
76+
if instance.PublicIP == "" {
77+
return nil, fmt.Errorf("public IP is not available for instance %s", instance.CloudID)
4778
}
4879

49-
// Connect to the instance via SSH
5080
sshClient, err := ssh.ConnectToHost(ctx, ssh.ConnectionConfig{
51-
User: sshUser,
52-
HostPort: fmt.Sprintf("%s:%d", publicIP, sshPort),
81+
User: instance.SSHUser,
82+
HostPort: fmt.Sprintf("%s:%d", instance.PublicIP, instance.SSHPort),
5383
PrivKey: privateKey,
5484
})
5585
if err != nil {
56-
return fmt.Errorf("failed to connect to instance via SSH: %w", err)
86+
return nil, fmt.Errorf("failed to connect to instance via SSH: %w", err)
5787
}
58-
defer func() {
59-
if closeErr := sshClient.Close(); closeErr != nil {
60-
// Log close error but don't return it as it's not the primary error
61-
fmt.Printf("warning: failed to close SSH connection: %v\n", closeErr)
62-
}
63-
}()
88+
return sshClient, nil
89+
}
6490

65-
// Check 1: Verify x86_64 architecture
91+
func validateArchitecture(ctx context.Context, sshClient *ssh.Client) (string, error) {
6692
stdout, stderr, err := sshClient.RunCommand(ctx, "uname -m")
6793
if err != nil {
68-
return fmt.Errorf("failed to check architecture: %w, stdout: %s, stderr: %s", err, stdout, stderr)
94+
return "", fmt.Errorf("failed to check architecture: %w, stdout: %s, stderr: %s", err, stdout, stderr)
6995
}
70-
if !strings.Contains(strings.TrimSpace(stdout), "x86_64") {
71-
return fmt.Errorf("expected x86_64 architecture, got: %s", strings.TrimSpace(stdout))
96+
arch := strings.TrimSpace(stdout)
97+
if !strings.Contains(arch, "x86_64") {
98+
return "", fmt.Errorf("expected x86_64 architecture, got: %s", arch)
7299
}
100+
return "x86_64", nil
101+
}
73102

74-
// Check 2: Verify Ubuntu 20.04 or 22.04
75-
stdout, stderr, err = sshClient.RunCommand(ctx, "cat /etc/os-release | grep PRETTY_NAME")
103+
func validateOSVersion(ctx context.Context, sshClient *ssh.Client) (string, error) {
104+
stdout, stderr, err := sshClient.RunCommand(ctx, "cat /etc/os-release | grep PRETTY_NAME")
76105
if err != nil {
77-
return fmt.Errorf("failed to check OS version: %w, stdout: %s, stderr: %s", err, stdout, stderr)
106+
return "", fmt.Errorf("failed to check OS version: %w, stdout: %s, stderr: %s", err, stdout, stderr)
78107
}
79108

80109
parts := strings.Split(strings.TrimSpace(stdout), "=")
81110
if len(parts) != 2 {
82-
return fmt.Errorf("error: os pretty name not in format PRETTY_NAME=\"Ubuntu\": %s", stdout)
111+
return "", fmt.Errorf("error: os pretty name not in format PRETTY_NAME=\"Ubuntu\": %s", stdout)
83112
}
84113

85-
// Remove quotes from the value
86114
osVersion := strings.Trim(parts[1], "\"")
87115
ubuntuRegex := regexp.MustCompile(`Ubuntu 20\.04|22\.04`)
88116
if !ubuntuRegex.MatchString(osVersion) {
89-
return fmt.Errorf("expected Ubuntu 20.04 or 22.04, got: %s", osVersion)
117+
return "", fmt.Errorf("expected Ubuntu 20.04 or 22.04, got: %s", osVersion)
90118
}
119+
return osVersion, nil
120+
}
91121

92-
// Check 3: Verify home directory
93-
stdout, stderr, err = sshClient.RunCommand(ctx, "cd ~ && pwd")
122+
func validateHomeDirectory(ctx context.Context, sshClient *ssh.Client, sshUser string) (string, error) {
123+
stdout, stderr, err := sshClient.RunCommand(ctx, "cd ~ && pwd")
94124
if err != nil {
95-
return fmt.Errorf("failed to check home directory: %w, stdout: %s, stderr: %s", err, stdout, stderr)
125+
return "", fmt.Errorf("failed to check home directory: %w, stdout: %s, stderr: %s", err, stdout, stderr)
96126
}
97127

98128
homeDir := strings.TrimSpace(stdout)
99129
if sshUser == "ubuntu" {
100130
if !strings.Contains(homeDir, "/home/ubuntu") {
101-
return fmt.Errorf("expected ubuntu user home directory to contain /home/ubuntu, got: %s", homeDir)
131+
return "", fmt.Errorf("expected ubuntu user home directory to contain /home/ubuntu, got: %s", homeDir)
102132
}
103133
} else {
104134
if !strings.Contains(homeDir, "/root") {
105-
return fmt.Errorf("expected non-ubuntu user home directory to contain /root, got: %s", homeDir)
135+
return "", fmt.Errorf("expected non-ubuntu user home directory to contain /root, got: %s", homeDir)
106136
}
107137
}
138+
return homeDir, nil
139+
}
108140

109-
fmt.Printf("Instance image validation passed for %s: architecture=%s, os=%s, home=%s\n",
110-
instance.CloudID, "x86_64", osVersion, homeDir)
141+
func validateSystemd(ctx context.Context, sshClient *ssh.Client) (string, error) {
142+
stdout, stderr, err := sshClient.RunCommand(ctx, "systemctl is-system-running")
143+
if err != nil {
144+
return "", fmt.Errorf("failed to check systemd status: %w, stdout: %s, stderr: %s", err, stdout, stderr)
145+
}
111146

112-
return nil
147+
systemdStatus := strings.TrimSpace(stdout)
148+
if systemdStatus != "running" && systemdStatus != "degraded" {
149+
return "", fmt.Errorf("expected systemd to be running or degraded, got: %s", systemdStatus)
150+
}
151+
return systemdStatus, nil
113152
}

0 commit comments

Comments
 (0)