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
2 changes: 1 addition & 1 deletion api/v1/git_server_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type GitServerSpec struct {
// For Gerrit provider, only id_rsa key is required and used.
// When id_rsa is present the operator connects over SSH and verifies the server's
// host key against the operator's ssh-known-hosts ConfigMap; hosts other than
// github.com, gitlab.com and bitbucket.org must be added there first.
// github.com, gitlab.com, bitbucket.org and ssh.bitbucket.org must be added there first.
// +kubebuilder:example:=my-git-credentials
// +required
NameSshKeySecret string `json:"nameSshKeySecret"`
Expand Down
2 changes: 1 addition & 1 deletion config/crd/bases/v2.edp.epam.com_gitservers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ spec:
For Gerrit provider, only id_rsa key is required and used.
When id_rsa is present the operator connects over SSH and verifies the server's
host key against the operator's ssh-known-hosts ConfigMap; hosts other than
github.com, gitlab.com and bitbucket.org must be added there first.
github.com, gitlab.com, bitbucket.org and ssh.bitbucket.org must be added there first.
example: my-git-credentials
type: string
skipWebhookSSLVerification:
Expand Down
2 changes: 1 addition & 1 deletion controllers/gitserver/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func checkGitServerConnection(data gitSshData, log logr.Logger) error {

func extractSshData(gitServer *model.GitServer, secret *corev1.Secret) gitSshData {
return gitSshData{
Host: gitServer.GitHost,
Host: util.SSHHost(gitServer.GitProvider, gitServer.GitHost),
User: gitServer.GitUser,
Key: string(secret.Data[util.PrivateSShKeyName]),
Port: gitServer.SshPort,
Expand Down
20 changes: 20 additions & 0 deletions controllers/gitserver/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import (
"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"

codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
"github.com/epam/edp-codebase-operator/v2/pkg/model"
"github.com/epam/edp-codebase-operator/v2/pkg/util"
)

func Test_sshInitFromSecret_verifiesHostKeys(t *testing.T) {
Expand All @@ -32,6 +37,21 @@ func Test_sshInitFromSecret_verifiesHostKeys(t *testing.T) {
assert.Equal(t, []string{"ssh-ed25519"}, client.Config.HostKeyAlgorithms)
}

func Test_extractSshData_bitbucketCloudUsesSSHHost(t *testing.T) {
t.Parallel()

data := extractSshData(&model.GitServer{
GitHost: "bitbucket.org",
GitProvider: codebaseApi.GitProviderBitbucket,
GitUser: "git",
SshPort: 22,
}, &corev1.Secret{Data: map[string][]byte{util.PrivateSShKeyName: []byte("key")}})

assert.Equal(t, "ssh.bitbucket.org", data.Host)
assert.Equal(t, int32(22), data.Port)
assert.Equal(t, "key", data.Key)
}

func Test_sshInitFromSecret_failsWithoutKnownHosts(t *testing.T) {
t.Setenv("SSH_KNOWN_HOSTS", filepath.Join(t.TempDir(), "absent"))

Expand Down
2 changes: 1 addition & 1 deletion deploy-templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ A Helm chart for KubeRocketCI Codebase Operator
| jira.quickLink | object | `{"enabled":true}` | Enable creation of QuickLink for Jira |
| jira.rootUrl | string | `"https://jiraeu.example.com"` | URL to Jira server |
| knownHosts.entries | string | `""` (no self-hosted servers pinned) | Host keys for self-hosted git servers, in known_hosts format, one per line. Obtain them with `ssh-keyscan -t rsa,ecdsa,ed25519 -p <port> <host>` and verify the fingerprints out-of-band before trusting them. Servers on a port other than 22 must use the bracket form, e.g. `[git.example.com]:2222 ssh-ed25519 AAAA...`. |
| knownHosts.includeDefaultProviders | bool | `true` | Include the shipped host keys for github.com, gitlab.com and bitbucket.org. Disable only if you pin these hosts yourself through `entries`. |
| knownHosts.includeDefaultProviders | bool | `true` | Include the shipped host keys for github.com, gitlab.com, bitbucket.org and ssh.bitbucket.org. Disable only if you pin these hosts yourself through `entries`. |
| name | string | `"codebase-operator"` | component name |
| nodeSelector | object | `{}` | |
| podLabels | object | `{}` | Labels to be added to the pod |
Expand Down
2 changes: 1 addition & 1 deletion deploy-templates/crds/v2.edp.epam.com_gitservers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ spec:
For Gerrit provider, only id_rsa key is required and used.
When id_rsa is present the operator connects over SSH and verifies the server's
host key against the operator's ssh-known-hosts ConfigMap; hosts other than
github.com, gitlab.com and bitbucket.org must be added there first.
github.com, gitlab.com, bitbucket.org and ssh.bitbucket.org must be added there first.
example: my-git-credentials
type: string
skipWebhookSSLVerification:
Expand Down
3 changes: 3 additions & 0 deletions deploy-templates/templates/ssh-known-hosts-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ data:
bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
ssh.bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
ssh.bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
ssh.bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
{{- end }}
{{- with .Values.knownHosts.entries }}
{{- tpl . $ | nindent 4 }}
Expand Down
2 changes: 1 addition & 1 deletion deploy-templates/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ branchStaleCheckInterval: 24h
# against these entries; there is no way to disable verification. GitServers that
# authenticate with a token over HTTPS are unaffected.
knownHosts:
# -- Include the shipped host keys for github.com, gitlab.com and bitbucket.org.
# -- Include the shipped host keys for github.com, gitlab.com, bitbucket.org and ssh.bitbucket.org.
# Disable only if you pin these hosts yourself through `entries`.
includeDefaultProviders: true
# -- Host keys for self-hosted git servers, in known_hosts format, one per line.
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1349,7 +1349,7 @@ Optional keys:
For Gerrit provider, only id_rsa key is required and used.
When id_rsa is present the operator connects over SSH and verifies the server's
host key against the operator's ssh-known-hosts ConfigMap; hosts other than
github.com, gitlab.com and bitbucket.org must be added there first.<br/>
github.com, gitlab.com, bitbucket.org and ssh.bitbucket.org must be added there first.<br/>
</td>
<td>true</td>
</tr><tr>
Expand Down
14 changes: 10 additions & 4 deletions docs/ssh-known-hosts.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ it into the operator at `/etc/codebase-operator/ssh`. The operator reads
`/etc/codebase-operator/ssh/ssh_known_hosts`, via the `SSH_KNOWN_HOSTS`
environment variable set by the chart.

The ConfigMap ships with host keys for `github.com`, `gitlab.com` and
`bitbucket.org`, so SSH GitServers pointing at those providers work with no
further configuration.
The ConfigMap ships with host keys for `github.com`, `gitlab.com`,
`bitbucket.org` and `ssh.bitbucket.org`, so SSH GitServers pointing at those
providers work with no further configuration.

Bitbucket Cloud moves SSH to `ssh.bitbucket.org`; SSH to `bitbucket.org` is
refused after 2026-12-10. Keep `gitHost: bitbucket.org` on the GitServer: the
operator uses `ssh.bitbucket.org` for SSH and `bitbucket.org` for HTTPS and
links. With `knownHosts.includeDefaultProviders: false`, add the
`ssh.bitbucket.org` keys to `knownHosts.entries`.

The ConfigMap is mounted as a directory rather than with `subPath`, so the
kubelet refreshes it in place and the operator re-reads it on the next
Expand Down Expand Up @@ -124,7 +130,7 @@ kubectl get gitservers -o json | jq -r '.items[] | select(.spec.gitProvider) |
```

For each one whose secret contains an `id_rsa` key and whose host is not
github.com, gitlab.com or bitbucket.org, add its host keys to
github.com, gitlab.com, bitbucket.org or ssh.bitbucket.org, add its host keys to
`knownHosts.entries` as part of the upgrade. GitServers left unpinned will
report `connected: false` until their keys are added; no data is lost and the
operator recovers on the next reconcile once the entry is present.
2 changes: 2 additions & 0 deletions pkg/model/git-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type ActionLog struct {

type GitServer struct {
GitHost string
GitProvider string
GitUser string
HttpsPort int32
SshPort int32
Expand All @@ -47,6 +48,7 @@ func ConvertToGitServer(k8sObj *codebaseApi.GitServer) *GitServer {

return &GitServer{
GitHost: spec.GitHost,
GitProvider: spec.GitProvider,
GitUser: spec.GitUser,
HttpsPort: spec.HttpsPort,
SshPort: spec.SshPort,
Expand Down
6 changes: 4 additions & 2 deletions pkg/model/git-server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ func TestConvertToGitServer(t *testing.T) {

gs := ConvertToGitServer(&codebaseApi.GitServer{
Spec: codebaseApi.GitServerSpec{
GitHost: "github.com",
GitHost: "github.com",
GitProvider: codebaseApi.GitProviderGithub,
},
})

assert.Equal(t, gs.GitHost, "github.com")
assert.Equal(t, "github.com", gs.GitHost)
assert.Equal(t, codebaseApi.GitProviderGithub, gs.GitProvider)
}
3 changes: 3 additions & 0 deletions pkg/util/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const (

GithubDomain = "https://github.com/epmd-edp"

BitbucketCloudHost = "bitbucket.org"
BitbucketCloudSSHHost = "ssh.bitbucket.org"

CITekton = "tekton"
CIGitLab = "gitlab"

Expand Down
16 changes: 14 additions & 2 deletions pkg/util/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,25 @@ func GetHostWithProtocol(host string) string {
return fmt.Sprintf("https://%v", host)
}

// SSHHost returns the host for SSH connections to a git server.
// Bitbucket Cloud SSH endpoint: ssh.bitbucket.org. HTTPS and web links stay on gitHost.
func SSHHost(gitProvider, gitHost string) string {
if gitProvider == codebaseApi.GitProviderBitbucket && strings.EqualFold(gitHost, BitbucketCloudHost) {
return BitbucketCloudSSHHost
}

return gitHost
}

// GetSSHUrl returns ssh url for git server.
func GetSSHUrl(gitServer *codebaseApi.GitServer, repoName string) string {
host := SSHHost(gitServer.Spec.GitProvider, gitServer.Spec.GitHost)

if gitServer.Spec.GitProvider == codebaseApi.GitProviderGerrit {
return fmt.Sprintf("ssh://%s:%d/%s", gitServer.Spec.GitHost, gitServer.Spec.SshPort, repoName)
return fmt.Sprintf("ssh://%s:%d/%s", host, gitServer.Spec.SshPort, repoName)
}

return fmt.Sprintf("git@%s:%s.git", gitServer.Spec.GitHost, repoName)
return fmt.Sprintf("git@%s:%s.git", host, repoName)
}

// GetHTTPSUrl returns https url for git server and codebase.
Expand Down
62 changes: 62 additions & 0 deletions pkg/util/url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,18 @@ func TestGetSSHUrl(t *testing.T) {
repoName: "owner/repo",
want: "git@github.com:owner/repo.git",
},
{
name: "should use ssh.bitbucket.org for bitbucket cloud",
gitServer: &codebaseApi.GitServer{
Spec: codebaseApi.GitServerSpec{
GitHost: "bitbucket.org",
GitProvider: codebaseApi.GitProviderBitbucket,
SshPort: 22,
},
},
repoName: "workspace/repo",
want: "git@ssh.bitbucket.org:workspace/repo.git",
},
}

for _, tt := range tests {
Expand All @@ -288,6 +300,56 @@ func TestGetSSHUrl(t *testing.T) {
}
}

func TestSSHHost(t *testing.T) {
t.Parallel()

tests := []struct {
name string
gitProvider string
gitHost string
want string
}{
{
name: "bitbucket cloud maps to ssh host",
gitProvider: codebaseApi.GitProviderBitbucket,
gitHost: "bitbucket.org",
want: "ssh.bitbucket.org",
},
{
name: "bitbucket cloud host is case-insensitive",
gitProvider: codebaseApi.GitProviderBitbucket,
gitHost: "BitBucket.org",
want: "ssh.bitbucket.org",
},
{
name: "ssh.bitbucket.org is kept",
gitProvider: codebaseApi.GitProviderBitbucket,
gitHost: "ssh.bitbucket.org",
want: "ssh.bitbucket.org",
},
{
name: "self-hosted bitbucket is kept",
gitProvider: codebaseApi.GitProviderBitbucket,
gitHost: "bitbucket.example.com",
want: "bitbucket.example.com",
},
{
name: "other provider with bitbucket.org host is kept",
gitProvider: codebaseApi.GitProviderGithub,
gitHost: "bitbucket.org",
want: "bitbucket.org",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

assert.Equal(t, tt.want, SSHHost(tt.gitProvider, tt.gitHost))
})
}
}

func TestGetProjectGitUrl(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading