As we reevaluate how to best support and maintain Staging Ref in the future, we encourage development teams using this environment to highlight their use cases in the following issue: https://gitlab.com/gitlab-com/gl-infra/software-delivery/framework/software-delivery-framework-issue-tracker/-/issues/36.

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • idrozdov/gitlab-shell
  • mmj/gitlab-shell
2 results
Show changes
Commits on Source (2)
Showing
with 363 additions and 48 deletions
Loading
Loading
@@ -10,6 +10,9 @@ include:
variables:
DOCKER_VERSION: "20.10.15"
BUNDLE_FROZEN: "true"
GO_VERSION: "1.18"
DEBIAN_VERSION: "bullseye"
RUBY_VERSION: "2.7"
workflow:
rules: &workflow_rules
Loading
Loading
@@ -21,7 +24,7 @@ workflow:
- if: '$CI_COMMIT_TAG'
default:
image: golang:1.14
image: registry.gitlab.com/gitlab-org/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}:git-2.36
tags:
- gitlab-org
Loading
Loading
@@ -37,13 +40,7 @@ default:
variables:
GITALY_CONNECTION_INFO: '{"address":"tcp://gitaly:8075", "storage":"default"}'
before_script:
# Set up the environment to run integration tests (still written in Ruby)
- apt-get update -qq && apt-get install -y ruby ruby-dev
- ruby -v
- export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
- gem install --force --bindir /usr/local/bin bundler -v 2.3.6
- bundle install
# Now set up to run the Golang tests
- make build
- cp config.yml.example config.yml
- go version
Loading
Loading
@@ -58,7 +55,6 @@ default:
tests:
extends: .test
image: golang:${GO_VERSION}
parallel:
matrix:
- GO_VERSION: ["1.17", "1.18", "1.19"]
Loading
Loading
@@ -68,7 +64,6 @@ tests:
race:
extends: .test
image: golang:1.18
script:
- make test_golang_race
Loading
Loading
Loading
Loading
@@ -44,6 +44,20 @@ func NewWithKey(gitlabKeyId string, env sshenv.Env, config *config.Config, readW
return nil, disallowedcommand.Error
}
func NewWithKrb5Principal(gitlabKrb5Principal string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
args, err := Parse(nil, env)
if err != nil {
return nil, err
}
args.GitlabKrb5Principal = gitlabKrb5Principal
if cmd := Build(args, config, readWriter); cmd != nil {
return cmd, nil
}
return nil, disallowedcommand.Error
}
func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
args := &commandargs.Shell{Arguments: arguments, Env: env}
Loading
Loading
Loading
Loading
@@ -71,6 +71,8 @@ func main() {
cfg.GitalyClient.InitSidechannelRegistry(ctx)
sshd.LoadGSSAPILib(&cfg.Server.GSSAPI)
server, err := sshd.NewServer(cfg)
if err != nil {
log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
Loading
Loading
Loading
Loading
@@ -107,3 +107,11 @@ sshd:
- /run/secrets/ssh-hostkeys/ssh_host_rsa_key-cert.pub
- /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key-cert.pub
- /run/secrets/ssh-hostkeys/ssh_host_ed25519_key-cert.pub
# GSSAPI-related settings
gssapi:
# Enable the gssapi-with-mic authentication method. Defaults to false.
enabled: false
# Keytab path. Defaults to "", system default (usually /etc/krb5.keytab).
keytab: ""
# The Kerberos service name to be used by sshd. Defaults to "", accepts any service name in keytab file.
service_principal_name: ""
Loading
Loading
@@ -8,6 +8,7 @@ require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/mattn/go-shellwords v1.0.11
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b
github.com/otiai10/copy v1.4.2
github.com/pires/go-proxyproto v0.6.2
github.com/prometheus/client_golang v1.13.1
Loading
Loading
Loading
Loading
@@ -26,12 +26,13 @@ var (
)
type Shell struct {
Arguments []string
GitlabUsername string
GitlabKeyId string
SshArgs []string
CommandType CommandType
Env sshenv.Env
Arguments []string
GitlabUsername string
GitlabKeyId string
GitlabKrb5Principal string
SshArgs []string
CommandType CommandType
Env sshenv.Env
}
func (s *Shell) Parse() error {
Loading
Loading
Loading
Loading
@@ -23,6 +23,13 @@ const (
type YamlDuration time.Duration
type GSSAPIConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Keytab string `yaml:"keytab,omitempty"`
ServicePrincipalName string `yaml:"service_principal_name,omitempty"`
LibPath string
}
type ServerConfig struct {
Listen string `yaml:"listen,omitempty"`
ProxyProtocol bool `yaml:"proxy_protocol,omitempty"`
Loading
Loading
@@ -41,6 +48,7 @@ type ServerConfig struct {
MACs []string `yaml:"macs"`
KexAlgorithms []string `yaml:"kex_algorithms"`
Ciphers []string `yaml:"ciphers"`
GSSAPI GSSAPIConfig `yaml:"gssapi,omitempty"`
}
type HttpSettingsConfig struct {
Loading
Loading
Loading
Loading
@@ -22,13 +22,14 @@ type Client struct {
}
type Request struct {
Action commandargs.CommandType `json:"action"`
Repo string `json:"project"`
Changes string `json:"changes"`
Protocol string `json:"protocol"`
KeyId string `json:"key_id,omitempty"`
Username string `json:"username,omitempty"`
CheckIp string `json:"check_ip,omitempty"`
Action commandargs.CommandType `json:"action"`
Repo string `json:"project"`
Changes string `json:"changes"`
Protocol string `json:"protocol"`
KeyId string `json:"key_id,omitempty"`
Username string `json:"username,omitempty"`
Krb5Principal string `json:"krb5principal,omitempty"`
CheckIp string `json:"check_ip,omitempty"`
}
type Gitaly struct {
Loading
Loading
@@ -81,6 +82,8 @@ func (c *Client) Verify(ctx context.Context, args *commandargs.Shell, action com
if args.GitlabUsername != "" {
request.Username = args.GitlabUsername
} else if args.GitlabKrb5Principal != "" {
request.Krb5Principal = args.GitlabKrb5Principal
} else {
request.KeyId = args.GitlabKeyId
}
Loading
Loading
Loading
Loading
@@ -56,7 +56,7 @@ func buildExpectedResponse(who string) *Response {
func TestSuccessfulResponses(t *testing.T) {
okResponse := testResponse{body: responseBody(t, "allowed.json"), status: http.StatusOK}
client := setup(t,
map[string]testResponse{"first": okResponse},
map[string]testResponse{"first": okResponse, "test@TEST.TEST": okResponse},
map[string]testResponse{"1": okResponse},
)
Loading
Loading
@@ -73,6 +73,10 @@ func TestSuccessfulResponses(t *testing.T) {
desc: "Provide username within the request",
args: &commandargs.Shell{GitlabUsername: "first"},
who: "user-1",
}, {
desc: "Provide krb5principal within the request",
args: &commandargs.Shell{GitlabKrb5Principal: "test@TEST.TEST"},
who: "user-1",
},
}
Loading
Loading
@@ -255,6 +259,10 @@ func setup(t *testing.T, userResponses, keyResponses map[string]testResponse) *C
w.WriteHeader(tr.status)
_, err := w.Write(tr.body)
require.NoError(t, err)
} else if tr, ok := userResponses[requestBody.Krb5Principal]; ok {
w.WriteHeader(tr.status)
_, err := w.Write(tr.body)
require.NoError(t, err)
} else if tr, ok := keyResponses[requestBody.KeyId]; ok {
w.WriteHeader(tr.status)
_, err := w.Write(tr.body)
Loading
Loading
Loading
Loading
@@ -38,6 +38,8 @@ func (c *Client) GetByCommandArgs(ctx context.Context, args *commandargs.Shell)
params.Add("username", args.GitlabUsername)
} else if args.GitlabKeyId != "" {
params.Add("key_id", args.GitlabKeyId)
} else if args.GitlabKrb5Principal != "" {
params.Add("krb5principal", args.GitlabKrb5Principal)
} else {
// There was no 'who' information, this matches the ruby error
// message.
Loading
Loading
Loading
Loading
@@ -38,6 +38,13 @@ func init() {
Name: "Jane Doe",
}
json.NewEncoder(w).Encode(body)
} else if r.URL.Query().Get("krb5principal") == "john-doe@TEST.TEST" {
body := &Response{
UserId: 3,
Username: "john-doe",
Name: "John Doe",
}
json.NewEncoder(w).Encode(body)
} else if r.URL.Query().Get("username") == "broken_message" {
w.WriteHeader(http.StatusForbidden)
body := &client.ErrorResponse{
Loading
Loading
@@ -76,6 +83,16 @@ func TestGetByUsername(t *testing.T) {
require.Equal(t, &Response{UserId: 1, Username: "jane-doe", Name: "Jane Doe"}, result)
}
func TestGetByKrb5Principal(t *testing.T) {
client := setup(t)
params := url.Values{}
params.Add("krb5principal", "john-doe@TEST.TEST")
result, err := client.getResponse(context.Background(), params)
require.NoError(t, err)
require.Equal(t, &Response{UserId: 3, Username: "john-doe", Name: "John Doe"}, result)
}
func TestMissingUser(t *testing.T) {
client := setup(t)
Loading
Loading
package sshd
import (
"fmt"
"github.com/openshift/gssapi"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
"gitlab.com/gitlab-org/labkit/log"
)
var lib *gssapi.Lib
func LoadGSSAPILib(config *config.GSSAPIConfig) error {
var err error
if config.Enabled {
options := &gssapi.Options{
Krb5Ktname: config.Keytab,
}
if config.LibPath != "" {
options.LibPath = config.LibPath
}
lib, err = gssapi.Load(options)
if err != nil {
log.WithError(err).Error("Unable to load GSSAPI library, gssapi-with-mic is disabled")
config.Enabled = false
}
}
return err
}
type OSGSSAPIServer struct {
Keytab string
ServicePrincipalName string
contextId *gssapi.CtxId
}
func (_ *OSGSSAPIServer) str2name(str string) (*gssapi.Name, error) {
strBuffer, err := lib.MakeBufferString(str)
if err != nil {
return nil, err
}
defer strBuffer.Release()
return strBuffer.Name(lib.GSS_C_NO_OID)
}
func (server *OSGSSAPIServer) AcceptSecContext(
token []byte,
) (
outputToken []byte,
srcName string,
needContinue bool,
err error,
) {
tokenBuffer, err := lib.MakeBufferBytes(token)
if err != nil {
return
}
defer tokenBuffer.Release()
var spn *gssapi.CredId = lib.GSS_C_NO_CREDENTIAL
if server.ServicePrincipalName != "" {
var name *gssapi.Name
name, err = server.str2name(server.ServicePrincipalName)
if err != nil {
return
}
defer name.Release()
var actualMech *gssapi.OIDSet
spn, actualMech, _, err = lib.AcquireCred(name, 0, lib.GSS_C_NO_OID_SET, gssapi.GSS_C_ACCEPT)
if err != nil {
return
}
defer spn.Release()
defer actualMech.Release()
}
ctxOut, srcNameName, _, outputTokenBuffer, _, _, _, err := lib.AcceptSecContext(
server.contextId,
spn,
tokenBuffer,
nil,
)
if err == gssapi.ErrContinueNeeded {
needContinue = true
err = nil
} else if err != nil {
return
}
defer outputTokenBuffer.Release()
defer srcNameName.Release()
outputToken = outputTokenBuffer.Bytes()
server.contextId = ctxOut
return outputToken, srcNameName.String(), needContinue, err
}
func (server *OSGSSAPIServer) VerifyMIC(
micField []byte,
micToken []byte,
) error {
if server.contextId == nil {
return fmt.Errorf("gssapi: uninitialized contextId")
}
micFieldBuffer, err := lib.MakeBufferBytes(micField)
if err != nil {
return err
}
defer micFieldBuffer.Release()
micTokenBuffer, err := lib.MakeBufferBytes(micToken)
if err != nil {
return err
}
defer micTokenBuffer.Release()
_, err = server.contextId.VerifyMIC(micFieldBuffer, micTokenBuffer)
return err
}
func (server *OSGSSAPIServer) DeleteSecContext() error {
if server.contextId == nil {
return nil
}
err := server.contextId.DeleteSecContext()
if err == nil {
server.contextId = nil
}
return err
}
package sshd
import (
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
)
func TestLoadGSSAPILibSucces(t *testing.T) {
config := &config.GSSAPIConfig{Enabled: true}
err := LoadGSSAPILib(config)
require.NotNil(t, lib)
require.Nil(t, err)
require.True(t, config.Enabled)
}
func TestLoadGSSAPILibFailure(t *testing.T) {
config := &config.GSSAPIConfig{Enabled: true, LibPath: "/invalid"}
err := LoadGSSAPILib(config)
require.Nil(t, lib)
require.NotNil(t, err)
require.False(t, config.Enabled)
}
Loading
Loading
@@ -146,6 +146,26 @@ func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.Publ
}
func (s *serverConfig) get(ctx context.Context) *ssh.ServerConfig {
var gssapiWithMICConfig *ssh.GSSAPIWithMICConfig
if s.cfg.Server.GSSAPI.Enabled {
gssapiWithMICConfig = &ssh.GSSAPIWithMICConfig{
AllowLogin: func(conn ssh.ConnMetadata, srcName string) (*ssh.Permissions, error) {
if conn.User() != s.cfg.User {
return nil, fmt.Errorf("unknown user")
}
return &ssh.Permissions{
// Record the Kerberos principal used for authentication.
Extensions: map[string]string{
"krb5principal": srcName,
},
}, nil
},
Server: &OSGSSAPIServer{
ServicePrincipalName: s.cfg.Server.GSSAPI.ServicePrincipalName,
},
}
}
sshCfg := &ssh.ServerConfig{
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
res, err := s.getAuthKey(ctx, conn.User(), key)
Loading
Loading
@@ -160,7 +180,8 @@ func (s *serverConfig) get(ctx context.Context) *ssh.ServerConfig {
},
}, nil
},
ServerVersion: "SSH-2.0-GitLab-SSHD",
GSSAPIWithMICConfig: gssapiWithMICConfig,
ServerVersion: "SSH-2.0-GitLab-SSHD",
}
if len(s.cfg.Server.MACs) > 0 {
Loading
Loading
Loading
Loading
@@ -171,6 +171,52 @@ func TestCustomAlgorithms(t *testing.T) {
require.Equal(t, customCiphers, sshServerConfig.Ciphers)
}
func TestGSSAPIWithMIC(t *testing.T) {
srvCfg := &serverConfig{
cfg: &config.Config{
Server: config.ServerConfig{
GSSAPI: config.GSSAPIConfig{
Enabled: true,
ServicePrincipalName: "host/test@TEST.TEST",
},
},
},
}
sshServerConfig := srvCfg.get(context.Background())
server := sshServerConfig.GSSAPIWithMICConfig.Server.(*OSGSSAPIServer)
require.NotNil(t, sshServerConfig.GSSAPIWithMICConfig)
require.NotNil(t, sshServerConfig.GSSAPIWithMICConfig.AllowLogin)
require.NotNil(t, server)
require.Equal(t, server.ServicePrincipalName, "host/test@TEST.TEST")
sshServerConfig.SetDefaults()
require.NotNil(t, sshServerConfig.GSSAPIWithMICConfig)
require.NotNil(t, sshServerConfig.GSSAPIWithMICConfig.AllowLogin)
require.NotNil(t, server)
require.Equal(t, server.ServicePrincipalName, "host/test@TEST.TEST")
}
func TestGSSAPIWithMICDisabled(t *testing.T) {
srvCfg := &serverConfig{
cfg: &config.Config{
Server: config.ServerConfig{
GSSAPI: config.GSSAPIConfig{
Enabled: false,
},
},
},
}
sshServerConfig := srvCfg.get(context.Background())
require.Nil(t, sshServerConfig.GSSAPIWithMICConfig)
sshServerConfig.SetDefaults()
require.Nil(t, sshServerConfig.GSSAPIWithMICConfig)
}
func rsaPublicKey(t *testing.T) ssh.PublicKey {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@ import (
grpcstatus "google.golang.org/grpc/status"
shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
Loading
Loading
@@ -23,10 +24,11 @@ import (
type session struct {
// State set up by the connection
cfg *config.Config
channel ssh.Channel
gitlabKeyId string
remoteAddr string
cfg *config.Config
channel ssh.Channel
gitlabKeyId string
gitlabKrb5Principal string
remoteAddr string
// State managed by the session
execCmd string
Loading
Loading
@@ -166,7 +168,14 @@ func (s *session) handleShell(ctx context.Context, req *ssh.Request) (uint32, er
ErrOut: s.channel.Stderr(),
}
cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
var cmd command.Command
var err error
if s.gitlabKrb5Principal != "" {
cmd, err = shellCmd.NewWithKrb5Principal(s.gitlabKrb5Principal, env, s.cfg, rw)
} else {
cmd, err = shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
}
if err != nil {
if errors.Is(err, disallowedcommand.Error) {
s.toStderr(ctx, "ERROR: Unknown command: %v\n", s.execCmd)
Loading
Loading
Loading
Loading
@@ -130,21 +130,29 @@ func TestHandleExec(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
out := &bytes.Buffer{}
f := &fakeChannel{stdErr: out}
s := &session{
gitlabKeyId: "root",
channel: f,
cfg: &config.Config{GitlabUrl: url},
sessions := []*session{
{
gitlabKeyId: "root",
cfg: &config.Config{GitlabUrl: url},
},
{
gitlabKrb5Principal: "test@TEST.TEST",
cfg: &config.Config{GitlabUrl: url},
},
}
for _, s := range sessions {
out := &bytes.Buffer{}
f := &fakeChannel{stdErr: out}
r := &ssh.Request{Payload: tc.payload}
s.channel = f
shouldContinue, err := s.handleExec(context.Background(), r)
require.Equal(t, tc.expectedErr, err)
require.Equal(t, false, shouldContinue)
require.Equal(t, tc.sentRequestName, f.sentRequestName)
require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
}
r := &ssh.Request{Payload: tc.payload}
shouldContinue, err := s.handleExec(context.Background(), r)
require.Equal(t, tc.expectedErr, err)
require.Equal(t, false, shouldContinue)
require.Equal(t, tc.sentRequestName, f.sentRequestName)
require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
})
}
}
Loading
Loading
Loading
Loading
@@ -194,11 +194,12 @@ func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
conn := newConnection(s.Config, nconn)
conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
session := &session{
cfg: s.Config,
channel: channel,
gitlabKeyId: sconn.Permissions.Extensions["key-id"],
remoteAddr: remoteAddr,
started: time.Now(),
cfg: s.Config,
channel: channel,
gitlabKeyId: sconn.Permissions.Extensions["key-id"],
gitlabKrb5Principal: sconn.Permissions.Extensions["krb5principal"],
remoteAddr: remoteAddr,
started: time.Now(),
}
return session.handle(ctx, requests)
Loading
Loading