Initial support for SSH based authentification #21

Merged
adamm merged 1 commits from adrianSuSE/autogits:main into main 2025-04-02 11:35:56 +02:00
3 changed files with 125 additions and 62 deletions

View File

@@ -19,9 +19,11 @@ package common
*/
const (
GiteaTokenEnv = "GITEA_TOKEN"
ObsUserEnv = "OBS_USER"
ObsPasswordEnv = "OBS_PASSWORD"
GiteaTokenEnv = "GITEA_TOKEN"
ObsUserEnv = "OBS_USER"
ObsPasswordEnv = "OBS_PASSWORD"
ObsSshkeyEnv = "OBS_SSHKEY"
ObsSshkeyFileEnv = "OBS_SSHKEYFILE"
DefaultGitPrj = "_ObsPrj"
PrjLinksFile = "links.json"

View File

@@ -20,13 +20,20 @@ package common
import (
"bytes"
"encoding/base64"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"os/exec"
"regexp"
"net/http"
"net/url"
"slices"
"strings"
"syscall"
"time"
)
type ObsClient struct {
@@ -34,6 +41,8 @@ type ObsClient struct {
client *http.Client
user, password string
cookie string
sshkey string
sshkeyfile string
HomeProject string
}
@@ -45,10 +54,12 @@ func NewObsClient(host string) (*ObsClient, error) {
}
return &ObsClient{
baseUrl: baseUrl,
client: &http.Client{},
user: obsUser,
password: obsPassword,
baseUrl: baseUrl,
client: &http.Client{},
user: obsUser,
password: obsPassword,
sshkey: obsSshkey,
sshkeyfile: obsSshkeyFile,
HomeProject: fmt.Sprintf("home:%s", obsUser),
}, nil
@@ -139,13 +150,7 @@ func parseProjectMeta(data []byte) (*ProjectMeta, error) {
}
func (c *ObsClient) GetGroupMeta(gid string) (*GroupMeta, error) {
req, err := http.NewRequest("GET", c.baseUrl.JoinPath("group", gid).String(), nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.user, c.password)
res, err := c.client.Do(req)
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("group", gid).String(), nil)
if err != nil {
return nil, err
}
@@ -174,13 +179,7 @@ func (c *ObsClient) GetGroupMeta(gid string) (*GroupMeta, error) {
}
func (c *ObsClient) GetUserMeta(uid string) (*UserMeta, error) {
req, err := http.NewRequest("GET", c.baseUrl.JoinPath("person", uid).String(), nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.user, c.password)
res, err := c.client.Do(req)
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("person", uid).String(), nil)
if err != nil {
return nil, err
}
@@ -208,13 +207,100 @@ func (c *ObsClient) GetUserMeta(uid string) (*UserMeta, error) {
return &meta, nil
}
func (c *ObsClient) GetProjectMeta(project string) (*ProjectMeta, error) {
req, err := http.NewRequest("GET", c.baseUrl.JoinPath("source", project, "_meta").String(), nil)
func (c *ObsClient) ObsRequest(method string, url string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.user, c.password)
if body != nil {
req.Body = io.NopCloser(body)
}
if c.cookie != "" {
req.Header.Add("cookie", c.cookie)
}
res, err := c.client.Do(req)
if err == nil && res.StatusCode == 200 {
auth_cookie := res.Header.Get("set-cookie")
if auth_cookie != "" {
c.cookie = auth_cookie
}
return res, nil
}
if res.StatusCode == 401 {
// log.Printf("new authentication is needed ...")
if c.sshkey == "" {
req.SetBasicAuth(c.user, c.password)
} else {
www := res.Header.Get("www-authenticate")
// log.Printf("www-authenticate %s", www)
re := regexp.MustCompile(`Signature realm="(.*?)",headers="\(created\)`)
match := re.FindStringSubmatch(www)
if len(match) < 1 {
return nil, errors.New("No realm found")
}
realm := string(match[1])
sshKeygenPath, err := exec.LookPath("ssh-keygen")
if err != nil {
fmt.Println("ssh-keygen not found")
Outdated
Review

looks fine, but whitespace is broken. mix of tab/space?

looks fine, but whitespace is broken. mix of tab/space?
}
// SSH Sign
cmd := exec.Command(sshKeygenPath, "-Y", "sign", "-f", c.sshkeyfile, "-n", realm, "-q")
now := time.Now().Unix()
sigdata := fmt.Sprintf("(created): %d", now)
cmd.Stdin = strings.NewReader(sigdata)
stdout, err := cmd.Output()
if err != nil {
log.Panic("SSH sign error:%s", cmd.Stderr)
if exitError, ok := err.(*exec.ExitError); ok {
waitStatus := exitError.Sys().(syscall.WaitStatus)
exitCode := waitStatus.ExitStatus()
return nil, errors.New(fmt.Sprintf("ssh-keygen signature creation failed: %d", exitCode))
}
return nil, errors.New("ssh-keygen signature creation failed")
}
reg := regexp.MustCompile("(?s)-----BEGIN SSH SIGNATURE-----\n(.*?)\n-----END SSH SIGNATURE-----")
match = reg.FindStringSubmatch(string(stdout))
if len(match) < 2 {
return nil, errors.New("could not extract ssh signature")
}
signature, err := base64.StdEncoding.DecodeString(string(match[1]))
if err != nil {
return nil, err
}
signatureBase64 := base64.StdEncoding.EncodeToString(signature)
authorization := fmt.Sprintf(`keyId="%s",algorithm="ssh",headers="(created)",created=%d,signature="%s"`,
c.user, now, signatureBase64)
// log.Printf("Add Authorization Signature ", authorization)
req.Header.Add("Authorization", "Signature " + authorization)
}
}
// Another time with authentification header
// log.Printf("Trying again with authorization: %s", req.Header.Get("Authorization"))
res, err = c.client.Do(req)
if err != nil {
log.Panic("Authentification failed: %d", res.StatusCode)
return nil, err
}
// Store the cookie for next call
auth_cookie := res.Header.Get("set-cookie")
if auth_cookie != "" {
c.cookie = auth_cookie
}
return res, err
}
func (c *ObsClient) GetProjectMeta(project string) (*ProjectMeta, error) {
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("source", project, "_meta").String(), nil)
if err != nil {
return nil, err
@@ -238,12 +324,7 @@ func (c *ObsClient) GetProjectMeta(project string) (*ProjectMeta, error) {
}
func (c *ObsClient) GetPackageMeta(project, pkg string) (*PackageMeta, error) {
req, err := http.NewRequest("GET", c.baseUrl.JoinPath("source", project, pkg, "_meta").String(), nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.user, c.password)
res, err := c.client.Do(req)
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("source", project, pkg, "_meta").String(), nil)
if err != nil {
return nil, err
@@ -311,19 +392,12 @@ func (c *ObsClient) SetProjectMeta(meta *ProjectMeta) error {
}
}
req, err := http.NewRequest("PUT", c.baseUrl.JoinPath("source", meta.Name, "_meta").String(), nil)
if err != nil {
return err
}
req.SetBasicAuth(c.user, c.password)
xml, err := xml.Marshal(meta)
if err != nil {
return err
}
req.Body = io.NopCloser(bytes.NewReader(xml))
log.Printf("xml: %s", xml)
res, err := c.client.Do(req)
res, err := c.ObsRequest("PUT", c.baseUrl.JoinPath("source", meta.Name, "_meta").String(), io.NopCloser(bytes.NewReader(xml)))
if err != nil {
return err
}
@@ -339,14 +413,7 @@ func (c *ObsClient) SetProjectMeta(meta *ProjectMeta) error {
}
func (c *ObsClient) DeleteProject(project string) error {
req, err := http.NewRequest("DELETE", c.baseUrl.JoinPath("source", project).String(), nil)
if err != nil {
return err
}
req.SetBasicAuth(c.user, c.password)
res, err := c.client.Do(req)
res, err := c.ObsRequest("DELETE", c.baseUrl.JoinPath("source", project).String(), nil)
if err != nil {
return err
}
@@ -597,10 +664,7 @@ func (obs ObsProjectNotFound) Error() string {
func (c *ObsClient) ProjectConfig(project string) (string, error) {
u := c.baseUrl.JoinPath("source", project, "_config")
req, err := http.NewRequest("GET", u.String(), nil)
req.SetBasicAuth(c.user, c.password)
res, err := c.client.Do(req)
res, err := c.ObsRequest("GET", u.String(), nil)
if err != nil {
return "", err
}
@@ -625,15 +689,7 @@ func (c *ObsClient) BuildStatus(project string, packages ...string) (*BuildResul
}
}
u.RawQuery = query.Encode()
req, err := http.NewRequest("GET", u.String(), nil)
log.Print(u.String())
if err != nil {
return nil, err
}
req.SetBasicAuth(c.user, c.password)
res, err := c.client.Do(req)
res, err := c.ObsRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}

View File

@@ -30,7 +30,7 @@ const (
)
var giteaToken string
var obsUser, obsPassword string
var obsUser, obsPassword, obsSshkey, obsSshkeyFile string
var rabbitUser, rabbitPassword string
func RequireGiteaSecretToken() error {
@@ -54,9 +54,14 @@ func GetGiteaToken() string {
func RequireObsSecretToken() error {
obsUser = os.Getenv(ObsUserEnv)
obsPassword = os.Getenv(ObsPasswordEnv)
obsSshkey = os.Getenv(ObsSshkeyEnv)
obsSshkeyFile = os.Getenv(ObsSshkeyFileEnv)
if len(obsPassword) < 10 || len(obsUser) < 2 {
return fmt.Errorf("Missing OBS authentication: %s %s", ObsUserEnv, ObsPasswordEnv)
if len(obsUser) < 2 {
return fmt.Errorf("Missing OBS authentication user")
}
if len(obsPassword) < 10 && len(obsSshkeyFile) < 2 {
return fmt.Errorf("Missing OBS authentication, either password or sshkey file need to be set")
}
err := os.Setenv(ObsUserEnv, "")