Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1584,10 +1584,17 @@ func validateSubPath(p string) error {

// skillsInitData holds the template data for the unified skills-init script.
type skillsInitData struct {
AuthMountPath string // "/git-auth" or "" (for git auth)
GitRefs []gitRefData // git repos to clone
OCIRefs []ociRefData // OCI images to pull
InsecureOCI bool // --insecure flag for krane
AuthMountPath string // "/git-auth" or "" (for git auth)
GitRefs []gitRefData // git repos to clone
OCIRefs []ociRefData // OCI images to pull
InsecureOCI bool // --insecure flag for krane
SSHHosts []sshHostData // extra hosts to add to known_hosts via ssh-keyscan
}

// sshHostData holds the host and optional port for an SSH known_hosts entry.
type sshHostData struct {
Host string // hostname or IP
Port string // port number, empty means default (22)
}

// gitRefData holds pre-computed fields for each git skill ref, used by the script template.
Expand Down Expand Up @@ -1651,6 +1658,32 @@ func prepareSkillsInitData(

if authSecretRef != nil {
data.AuthMountPath = "/git-auth"
seenHosts := make(map[string]bool)
hostPattern := regexp.MustCompile(`^[A-Za-z0-9\.\-:]+$`)
portPattern := regexp.MustCompile(`^[0-9]+$`)
for _, ref := range gitRefs {
u, err := url.Parse(ref.URL)
if err != nil || u.Scheme != "ssh" {
continue
}
host := u.Hostname()
if host == "" || !hostPattern.MatchString(host) {
continue
}
port := u.Port()
if port == "22" {
port = "" // 22 is the SSH default; omit to avoid -p flag
}
if port != "" && !portPattern.MatchString(port) {
continue
}
key := host + ":" + port
if seenHosts[key] {
continue
}
seenHosts[key] = true
data.SSHHosts = append(data.SSHHosts, sshHostData{Host: host, Port: port})
}
}

seen := make(map[string]bool)
Expand Down
56 changes: 48 additions & 8 deletions go/core/internal/controller/translator/agent/git_skills_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package agent_test

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -44,13 +45,14 @@ func Test_AdkApiTranslator_Skills(t *testing.T) {
name string
agent *v1alpha2.Agent
// assertions
wantSkillsInit bool
wantSkillsVolume bool
wantContainsBranch string
wantContainsCommit string
wantContainsPath string
wantContainsKrane bool
wantAuthVolume bool
wantSkillsInit bool
wantSkillsVolume bool
wantContainsBranch string
wantContainsCommit string
wantContainsPath string
wantContainsKrane bool
wantAuthVolume bool
wantSSHKeyscanHosts []string // substrings expected in the ssh-keyscan lines
}{
{
name: "no skills - no init containers",
Expand Down Expand Up @@ -215,6 +217,34 @@ func Test_AdkApiTranslator_Skills(t *testing.T) {
wantSkillsVolume: true,
wantAuthVolume: true,
},
{
name: "git skills with SSH URL and auth secret scans custom host",
agent: &v1alpha2.Agent{
ObjectMeta: metav1.ObjectMeta{Name: "agent-ssh", Namespace: namespace},
Spec: v1alpha2.AgentSpec{
Type: v1alpha2.AgentType_Declarative,
Declarative: &v1alpha2.DeclarativeAgentSpec{
SystemMessage: "test",
ModelConfig: modelName,
},
Skills: &v1alpha2.SkillForAgent{
GitAuthSecretRef: &corev1.LocalObjectReference{
Name: "gitea-ssh-credentials",
},
GitRefs: []v1alpha2.GitRepo{
{
URL: "ssh://git@gitea-ssh.gitea:22/gitops/ssh-skills-repo.git",
Ref: "main",
},
},
},
},
},
wantSkillsInit: true,
wantSkillsVolume: true,
wantAuthVolume: true,
wantSSHKeyscanHosts: []string{"gitea-ssh.gitea"},
},
{
name: "git skill with custom name",
agent: &v1alpha2.Agent{
Expand Down Expand Up @@ -358,7 +388,7 @@ func Test_AdkApiTranslator_Skills(t *testing.T) {
for _, v := range deployment.Spec.Template.Spec.Volumes {
if v.Secret != nil && v.Name == "git-auth" {
hasAuthVolume = true
assert.Equal(t, "github-token", v.Secret.SecretName, "auth volume should reference the correct secret")
assert.Equal(t, tt.agent.Spec.Skills.GitAuthSecretRef.Name, v.Secret.SecretName, "auth volume should reference the correct secret")
}
}
assert.True(t, hasAuthVolume, "git-auth volume should exist")
Expand All @@ -378,6 +408,16 @@ func Test_AdkApiTranslator_Skills(t *testing.T) {
assert.Contains(t, script, "credential.helper")
}

// Verify custom SSH hosts are scanned
if len(tt.wantSSHKeyscanHosts) > 0 {
require.NotNil(t, skillsInitContainer)
script := skillsInitContainer.Command[2]
for _, host := range tt.wantSSHKeyscanHosts {
expected := fmt.Sprintf("ssh-keyscan %s", host)
assert.Contains(t, script, expected, "script should ssh-keyscan custom host %q", host)
}
}

// Verify insecure flag for OCI skills
if tt.agent.Spec.Skills != nil && tt.agent.Spec.Skills.InsecureSkipVerify {
require.NotNil(t, skillsInitContainer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ if [ -f "${_auth_mount}/ssh-privatekey" ]; then
cp "${_auth_mount}/ssh-privatekey" ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com gitlab.com bitbucket.org >> ~/.ssh/known_hosts
{{- range .SSHHosts }}
{{- if .Port }}
ssh-keyscan -p {{ .Port }} {{ .Host }} >> ~/.ssh/known_hosts
{{- else }}
ssh-keyscan {{ .Host }} >> ~/.ssh/known_hosts
{{- end }}
{{- end }}
elif [ -f "${_auth_mount}/token" ]; then
git config --global credential.helper "!f() { echo username=x-access-token; echo password=\$(cat ${_auth_mount}/token); }; f"
fi
Expand Down
Loading