Skip to content

Commit 8a168fa

Browse files
authored
Merge pull request #24 from brevdev/devin/1754788560-add-systemd-validation
Add systemd validation check to ValidateInstanceImage
2 parents d5451ae + 0c308fb commit 8a168fa

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

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: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,84 +30,125 @@ 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+
}
3762

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)
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+
}
68+
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")
111143

112-
return nil
144+
systemdStatus := strings.TrimSpace(stdout)
145+
if systemdStatus == "running" || systemdStatus == "degraded" {
146+
return systemdStatus, nil
147+
}
148+
149+
if err != nil {
150+
return "", fmt.Errorf("failed to check systemd status: %w, stdout: %s, stderr: %s", err, stdout, stderr)
151+
}
152+
153+
return "", fmt.Errorf("expected systemd to be running or degraded, got: %s", systemdStatus)
113154
}

0 commit comments

Comments
 (0)