fix: add involves edge from task to agent:nomos at creation
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
2026-08-11 22:03:12 +02:00
parent 7d6a3320d4
commit febc153b7f
3384 changed files with 945212 additions and 2 deletions

1
vendor/github.com/zalando/go-keyring/.catwatch.yml generated vendored Normal file
View File

@@ -0,0 +1 @@
title: go-keyring

23
vendor/github.com/zalando/go-keyring/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,23 @@
# https://github.com/github/gitignore
######################### Go ###################################################
# https://raw.githubusercontent.com/github/gitignore/master/Go.gitignore
################################################################################
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# Go workspace file
go.work

8
vendor/github.com/zalando/go-keyring/.zappr.yml generated vendored Normal file
View File

@@ -0,0 +1,8 @@
approvals:
groups:
zalando:
minimum: 2
from:
orgs:
- "zalando"
X-Zalando-Team: teapot

12
vendor/github.com/zalando/go-keyring/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,12 @@
# Contributing to Go keyring library
Please open an issue for bugs and feature requests. We are open to Pull Requests, but we would like to discuss features with you.
If you want to submit a PR, always use feature branches and let people discuss changes in pull requests.
Pull requests should only be merged after all discussions have been concluded and at least 2 reviewers has given their
**approval**.
## Guidelines
- **every code change** should have a test
- keep the current code style

21
vendor/github.com/zalando/go-keyring/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Zalando SE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
vendor/github.com/zalando/go-keyring/MAINTAINERS generated vendored Normal file
View File

@@ -0,0 +1,2 @@
Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
Sandor Szücs <sandor.szuecs@zalando.de>

269
vendor/github.com/zalando/go-keyring/README.md generated vendored Normal file
View File

@@ -0,0 +1,269 @@
# Go Keyring library
[![Go Report Card](https://goreportcard.com/badge/github.com/zalando/go-keyring)](https://goreportcard.com/report/github.com/zalando/go-keyring)
[![GoDoc](https://godoc.org/github.com/zalando/go-keyring?status.svg)](https://godoc.org/github.com/zalando/go-keyring)
`go-keyring` is an OS-agnostic library for *setting*, *getting* and *deleting*
secrets from the system keyring. It supports **OS X**, **Linux/BSD (dbus)** and
**Windows**.
go-keyring was created after its authors searched for, but couldn't find, a better alternative. It aims to simplify
using statically linked binaries, which is cumbersome when relying on C bindings (as other keyring libraries do).
#### Potential Uses
If you're working with an application that needs to store user credentials
locally on the user's machine, go-keyring might come in handy. For instance, if you are writing a CLI for an API
that requires a username and password, you can store this information in the
keyring instead of having the user type it on every invocation.
## Dependencies
#### OS X
The OS X implementation depends on the `/usr/bin/security` binary for
interfacing with the OS X keychain. It should be available by default.
#### Linux and *BSD
The Linux and *BSD implementation depends on the [Secret Service][SecretService] dbus
interface, which is provided by [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring).
It's expected that the default collection `login` exists in the keyring, because
it's the default in most distros. If it doesn't exist, you can create it through the
keyring frontend program [Seahorse](https://wiki.gnome.org/Apps/Seahorse):
* Open `seahorse`
* Go to **File > New > Password Keyring**
* Click **Continue**
* When asked for a name, use: **login**
## Example Usage
How to *set* and *get* a secret from the keyring:
```go
package main
import (
"log"
"github.com/zalando/go-keyring"
)
func main() {
service := "my-app"
user := "anon"
password := "secret"
// set password
err := keyring.Set(service, user, password)
if err != nil {
log.Fatal(err)
}
// get password
secret, err := keyring.Get(service, user)
if err != nil {
log.Fatal(err)
}
log.Println(secret)
}
```
## Direct CLI Usage
While this library provides a convenient Go API, you can also interact with the system keyring directly using OS-specific command-line tools. This can be useful for debugging, scripting, or understanding what the library does under the hood. You can use the CLI to set-up the secrets from a script and then access them from Go, or vice-versa.
### macOS
macOS uses the `security` command to interact with the Keychain.
**Set a password:**
```bash
security add-generic-password -U -s "service" -a "user" -w "password"
```
**Get a password:**
```bash
security find-generic-password -s "service" -wa "user"
```
**Delete a password:**
```bash
security delete-generic-password -s "service" -a "user"
```
Where:
- `-s` specifies the service name
- `-a` specifies the account/username
- `-w` specifies the password to store
- `-U` updates the password if it already exists
- The `w` option in `-wa` outputs only the password value
### Linux and *BSD
Linux and *BSD systems use the Secret Service API via D-Bus. The easiest way to interact with it from the command line is using `secret-tool`, which is part of libsecret.
**Install secret-tool (if not already installed):**
```bash
# Debian/Ubuntu
sudo apt-get install libsecret-tools
# Fedora/RHEL
sudo dnf install libsecret
# Arch Linux
sudo pacman -S libsecret
```
**Set a password:**
```bash
secret-tool store --label="Password for 'user' on 'service'" service "service" username "user"
# You'll be prompted to enter the password
```
Or provide the password directly:
```bash
echo -n "password" | secret-tool store --label="Password for 'user' on 'service'" service "service" username "user"
```
**Get a password:**
```bash
secret-tool lookup service "service" username "user"
```
**Delete a password:**
```bash
secret-tool clear service "service" username "user"
```
Note: The `service` and `username` are attributes used to identify the secret. The label is a human-readable description.
### Windows
Windows uses the Credential Manager, which can be accessed via `cmdkey` or PowerShell.
**Using cmdkey:**
**Set a password:**
```cmd
cmdkey /generic:"service:user" /user:"user" /pass:"password"
```
**Get a password:**
`cmdkey` doesn't support retrieving passwords directly. Use PowerShell instead:
```powershell
$cred = Get-StoredCredential -Target "service:user"
$cred.GetNetworkCredential().Password
```
Or using the Windows API via PowerShell:
```powershell
[System.Net.NetworkCredential]::new("", (Get-StoredCredential -Target "service:user").Password).Password
```
**Delete a password:**
```cmd
cmdkey /delete:"service:user"
```
**Using PowerShell with CredentialManager module:**
First, install the CredentialManager module:
```powershell
Install-Module -Name CredentialManager -Force
```
**Set a password:**
```powershell
New-StoredCredential -Target "service:user" -UserName "user" -Password "password" -Type Generic -Persist LocalMachine
```
**Get a password:**
```powershell
(Get-StoredCredential -Target "service:user").GetNetworkCredential().Password
```
**Delete a password:**
```powershell
Remove-StoredCredential -Target "service:user"
```
Note: On Windows, the library combines the service and username as `service:username` for the credential target name.
## Tests
### Running tests
Running the tests is simple:
```
go test
```
Which OS you use *does* matter. If you're using **Linux** or **BSD**, it will
test the implementation in `keyring_unix.go`. If running the tests
on **OS X**, it will test the implementation in `keyring_darwin.go`.
### Mocking
If you need to mock the keyring behavior for testing on systems without a keyring implementation you can call `MockInit()` which will replace the OS defined provider with an in-memory one.
```go
package implementation
import (
"testing"
"github.com/zalando/go-keyring"
)
func TestMockedSetGet(t *testing.T) {
keyring.MockInit()
err := keyring.Set("service", "user", "password")
if err != nil {
t.Fatal(err)
}
p, err := keyring.Get("service", "user")
if err != nil {
t.Fatal(err)
}
if p != "password" {
t.Error("password was not the expected string")
}
}
```
## Contributing/TODO
We welcome contributions from the community; please use [CONTRIBUTING.md](CONTRIBUTING.md) as your guidelines for getting started. Here are some items that we'd love help with:
* The code base
* Better test coverage
Please use GitHub issues as the starting point for contributions, new ideas and/or bug reports.
## Contact
* E-Mail: <team-teapot@zalando.de>
* Security issues: Please send an email to the [maintainers](MAINTAINERS), and we'll try to get back to you within two workdays. If you don't hear back, send an email to <team-teapot@zalando.de> and someone will respond within five days max.
## Contributors
Thanks to:
* [your name here]
## License
See [LICENSE](LICENSE) file.
[SecretService]: https://specifications.freedesktop.org/secret-service-spec/latest/

8
vendor/github.com/zalando/go-keyring/SECURITY.md generated vendored Normal file
View File

@@ -0,0 +1,8 @@
We acknowledge that every line of code that we write may potentially contain security issues.
We are trying to deal with it responsibly and provide patches as quickly as possible.
We host our bug bounty program on HackerOne, it is currently private, therefore if you would like to report a vulnerability and get rewarded for it, please ask to join our program by filling this form:
https://corporate.zalando.com/en/services-and-contact#security-form
You can also send your report via this form if you do not want to join our bug bounty program and just want to report a vulnerability or security issue.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Alessio Treglia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,39 @@
/*
Package shellescape provides the shellescape.Quote to escape arbitrary
strings for a safe use as command line arguments in the most common
POSIX shells.
The original Python package which this work was inspired by can be found
at https://pypi.python.org/pypi/shellescape.
Portions of this file are from al.essio.dev/pkg/shellescape, © 2016 Alessio Treglia under the MIT License.
See LICENSE for more information.
*/
package shellescape
/*
The functionality provided by shellescape.Quote could be helpful
in those cases where it is known that the output of a Go program will
be appended to/used in the context of shell programs' command line arguments.
*/
import (
"regexp"
"strings"
)
var pattern *regexp.Regexp = regexp.MustCompile(`[^\w@%+=:,./-]`)
// Quote returns a shell-escaped version of the string s. The returned value
// is a string that can safely be used as one token in a shell command line.
func Quote(s string) string {
if len(s) == 0 {
return "''"
}
if pattern.MatchString(s) {
return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'"
}
return s
}

50
vendor/github.com/zalando/go-keyring/keyring.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
package keyring
import "errors"
// provider set in the init function by the relevant os file e.g.:
// keyring_unix.go
var provider Keyring = fallbackServiceProvider{}
var (
// ErrNotFound is the expected error if the secret isn't found in the
// keyring.
ErrNotFound = errors.New("secret not found in keyring")
// ErrSetDataTooBig is returned if `Set` was called with too much data.
// On MacOS: The combination of service, username & password should not exceed ~3000 bytes
// On Windows: The service is limited to 32KiB while the password is limited to 2560 bytes
// On Linux/Unix: There is no theoretical limit but performance suffers with big values (>100KiB)
ErrSetDataTooBig = errors.New("data passed to Set was too big")
)
// Keyring provides a simple set/get interface for a keyring service.
type Keyring interface {
// Set password in keyring for user.
Set(service, user, password string) error
// Get password from keyring given service and user name.
Get(service, user string) (string, error)
// Delete secret from keyring.
Delete(service, user string) error
// DeleteAll deletes all secrets for a given service
DeleteAll(service string) error
}
// Set password in keyring for user.
func Set(service, user, password string) error {
return provider.Set(service, user, password)
}
// Get password from keyring given service and user name.
func Get(service, user string) (string, error) {
return provider.Get(service, user)
}
// Delete secret from keyring.
func Delete(service, user string) error {
return provider.Delete(service, user)
}
// DeleteAll deletes all secrets for a given service
func DeleteAll(service string) error {
return provider.DeleteAll(service)
}

140
vendor/github.com/zalando/go-keyring/keyring_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package keyring
import (
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"os/exec"
"strings"
"github.com/zalando/go-keyring/internal/shellescape"
)
const (
execPathKeychain = "/usr/bin/security"
// encodingPrefix is a well-known prefix added to strings encoded by Set.
encodingPrefix = "go-keyring-encoded:"
base64EncodingPrefix = "go-keyring-base64:"
)
type macOSXKeychain struct{}
// func (*MacOSXKeychain) IsAvailable() bool {
// return exec.Command(execPathKeychain).Run() != exec.ErrNotFound
// }
// Get password from macos keyring given service and user name.
func (k macOSXKeychain) Get(service, username string) (string, error) {
out, err := exec.Command(
execPathKeychain,
"find-generic-password",
"-s", service,
"-wa", username).CombinedOutput()
if err != nil {
if strings.Contains(string(out), "could not be found") {
err = ErrNotFound
}
return "", err
}
trimStr := strings.TrimSpace(string(out[:]))
// if the string has the well-known prefix, assume it's encoded
if strings.HasPrefix(trimStr, encodingPrefix) {
dec, err := hex.DecodeString(trimStr[len(encodingPrefix):])
return string(dec), err
} else if strings.HasPrefix(trimStr, base64EncodingPrefix) {
dec, err := base64.StdEncoding.DecodeString(trimStr[len(base64EncodingPrefix):])
return string(dec), err
}
return trimStr, nil
}
// Set stores a secret in the macos keyring given a service name and a user.
func (k macOSXKeychain) Set(service, username, password string) error {
// if the added secret has multiple lines or some non ascii,
// osx will hex encode it on return. To avoid getting garbage, we
// encode all passwords
password = base64EncodingPrefix + base64.StdEncoding.EncodeToString([]byte(password))
cmd := exec.Command(execPathKeychain, "-i")
stdIn, err := cmd.StdinPipe()
if err != nil {
return err
}
if err = cmd.Start(); err != nil {
return err
}
command := fmt.Sprintf("add-generic-password -U -s %s -a %s -w %s\n", shellescape.Quote(service), shellescape.Quote(username), shellescape.Quote(password))
if len(command) > 4096 {
return ErrSetDataTooBig
}
if _, err := io.WriteString(stdIn, command); err != nil {
return err
}
if err = stdIn.Close(); err != nil {
return err
}
err = cmd.Wait()
return err
}
// Delete deletes a secret, identified by service & user, from the keyring.
func (k macOSXKeychain) Delete(service, username string) error {
out, err := exec.Command(
execPathKeychain,
"delete-generic-password",
"-s", service,
"-a", username).CombinedOutput()
if strings.Contains(string(out), "could not be found") {
err = ErrNotFound
}
return err
}
// DeleteAll deletes all secrets for a given service
func (k macOSXKeychain) DeleteAll(service string) error {
// if service is empty, do nothing otherwise it might accidentally delete all secrets
if service == "" {
return ErrNotFound
}
// Delete each secret in a while loop until there is no more left
// under the service
for {
out, err := exec.Command(
execPathKeychain,
"delete-generic-password",
"-s", service).CombinedOutput()
if strings.Contains(string(out), "could not be found") {
return nil
} else if err != nil {
return err
}
}
}
func init() {
provider = macOSXKeychain{}
}

View File

@@ -0,0 +1,27 @@
package keyring
import (
"errors"
"runtime"
)
// All of the following methods error out on unsupported platforms
var ErrUnsupportedPlatform = errors.New("unsupported platform: " + runtime.GOOS)
type fallbackServiceProvider struct{}
func (fallbackServiceProvider) Set(service, user, pass string) error {
return ErrUnsupportedPlatform
}
func (fallbackServiceProvider) Get(service, user string) (string, error) {
return "", ErrUnsupportedPlatform
}
func (fallbackServiceProvider) Delete(service, user string) error {
return ErrUnsupportedPlatform
}
func (fallbackServiceProvider) DeleteAll(service string) error {
return ErrUnsupportedPlatform
}

71
vendor/github.com/zalando/go-keyring/keyring_mock.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package keyring
type mockProvider struct {
mockStore map[string]map[string]string
mockError error
}
// Set stores user and pass in the keyring under the defined service
// name.
func (m *mockProvider) Set(service, user, pass string) error {
if m.mockError != nil {
return m.mockError
}
if m.mockStore == nil {
m.mockStore = make(map[string]map[string]string)
}
if m.mockStore[service] == nil {
m.mockStore[service] = make(map[string]string)
}
m.mockStore[service][user] = pass
return nil
}
// Get gets a secret from the keyring given a service name and a user.
func (m *mockProvider) Get(service, user string) (string, error) {
if m.mockError != nil {
return "", m.mockError
}
if b, ok := m.mockStore[service]; ok {
if v, ok := b[user]; ok {
return v, nil
}
}
return "", ErrNotFound
}
// Delete deletes a secret, identified by service & user, from the keyring.
func (m *mockProvider) Delete(service, user string) error {
if m.mockError != nil {
return m.mockError
}
if m.mockStore != nil {
if _, ok := m.mockStore[service]; ok {
if _, ok := m.mockStore[service][user]; ok {
delete(m.mockStore[service], user)
return nil
}
}
}
return ErrNotFound
}
// DeleteAll deletes all secrets for a given service
func (m *mockProvider) DeleteAll(service string) error {
if m.mockError != nil {
return m.mockError
}
delete(m.mockStore, service)
return nil
}
// MockInit sets the provider to a mocked memory store
func MockInit() {
provider = &mockProvider{}
}
// MockInitWithError sets the provider to a mocked memory store
// that returns the given error on all operations
func MockInitWithError(err error) {
provider = &mockProvider{mockError: err}
}

182
vendor/github.com/zalando/go-keyring/keyring_unix.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
//go:build (dragonfly && cgo) || (freebsd && cgo) || linux || netbsd || openbsd
package keyring
import (
"fmt"
dbus "github.com/godbus/dbus/v5"
ss "github.com/zalando/go-keyring/secret_service"
)
type secretServiceProvider struct{}
// Set stores user and pass in the keyring under the defined service
// name.
func (s secretServiceProvider) Set(service, user, pass string) error {
svc, err := ss.NewSecretService()
if err != nil {
return err
}
// open a session
session, err := svc.OpenSession()
if err != nil {
return err
}
defer svc.Close(session)
attributes := map[string]string{
"username": user,
"service": service,
}
secret := ss.NewSecret(session.Path(), pass)
collection := svc.GetLoginCollection()
err = svc.Unlock(collection.Path())
if err != nil {
return err
}
err = svc.CreateItem(collection,
fmt.Sprintf("Password for '%s' on '%s'", user, service),
attributes, secret)
if err != nil {
return err
}
return nil
}
// findItem looksup an item by service and user.
func (s secretServiceProvider) findItem(svc *ss.SecretService, service, user string) (dbus.ObjectPath, error) {
collection := svc.GetLoginCollection()
search := map[string]string{
"username": user,
"service": service,
}
err := svc.Unlock(collection.Path())
if err != nil {
return "", err
}
results, err := svc.SearchItems(collection, search)
if err != nil {
return "", err
}
if len(results) == 0 {
return "", ErrNotFound
}
return results[0], nil
}
// findServiceItems looksup all items by service.
func (s secretServiceProvider) findServiceItems(svc *ss.SecretService, service string) ([]dbus.ObjectPath, error) {
collection := svc.GetLoginCollection()
search := map[string]string{
"service": service,
}
err := svc.Unlock(collection.Path())
if err != nil {
return []dbus.ObjectPath{}, err
}
results, err := svc.SearchItems(collection, search)
if err != nil {
return []dbus.ObjectPath{}, err
}
if len(results) == 0 {
return []dbus.ObjectPath{}, ErrNotFound
}
return results, nil
}
// Get gets a secret from the keyring given a service name and a user.
func (s secretServiceProvider) Get(service, user string) (string, error) {
svc, err := ss.NewSecretService()
if err != nil {
return "", err
}
item, err := s.findItem(svc, service, user)
if err != nil {
return "", err
}
// open a session
session, err := svc.OpenSession()
if err != nil {
return "", err
}
defer svc.Close(session)
// unlock if invdividual item is locked
err = svc.Unlock(item)
if err != nil {
return "", err
}
secret, err := svc.GetSecret(item, session.Path())
if err != nil {
return "", err
}
return string(secret.Value), nil
}
// Delete deletes a secret, identified by service & user, from the keyring.
func (s secretServiceProvider) Delete(service, user string) error {
svc, err := ss.NewSecretService()
if err != nil {
return err
}
item, err := s.findItem(svc, service, user)
if err != nil {
return err
}
return svc.Delete(item)
}
// DeleteAll deletes all secrets for a given service
func (s secretServiceProvider) DeleteAll(service string) error {
// if service is empty, do nothing otherwise it might accidentally delete all secrets
if service == "" {
return ErrNotFound
}
svc, err := ss.NewSecretService()
if err != nil {
return err
}
// find all items for the service
items, err := s.findServiceItems(svc, service)
if err != nil {
if err == ErrNotFound {
return nil
}
return err
}
for _, item := range items {
err = svc.Delete(item)
if err != nil {
return err
}
}
return nil
}
func init() {
provider = secretServiceProvider{}
}

103
vendor/github.com/zalando/go-keyring/keyring_windows.go generated vendored Normal file
View File

@@ -0,0 +1,103 @@
package keyring
import (
"strings"
"syscall"
"github.com/danieljoos/wincred"
)
type windowsKeychain struct{}
// Get gets a secret from the keyring given a service name and a user.
func (k windowsKeychain) Get(service, username string) (string, error) {
cred, err := wincred.GetGenericCredential(k.credName(service, username))
if err != nil {
if err == syscall.ERROR_NOT_FOUND {
return "", ErrNotFound
}
return "", err
}
return string(cred.CredentialBlob), nil
}
// Set stores stores user and pass in the keyring under the defined service
// name.
func (k windowsKeychain) Set(service, username, password string) error {
// password may not exceed 2560 bytes (https://github.com/jaraco/keyring/issues/540#issuecomment-968329967)
if len(password) > 2560 {
return ErrSetDataTooBig
}
// service may not exceed 512 bytes (might need more testing)
if len(service) >= 512 {
return ErrSetDataTooBig
}
// service may not exceed 32k but problems occur before that
// so we limit it to 30k
if len(service) > 1024*30 {
return ErrSetDataTooBig
}
cred := wincred.NewGenericCredential(k.credName(service, username))
cred.UserName = username
cred.CredentialBlob = []byte(password)
return cred.Write()
}
// Delete deletes a secret, identified by service & user, from the keyring.
func (k windowsKeychain) Delete(service, username string) error {
cred, err := wincred.GetGenericCredential(k.credName(service, username))
if err != nil {
if err == syscall.ERROR_NOT_FOUND {
return ErrNotFound
}
return err
}
return cred.Delete()
}
func (k windowsKeychain) DeleteAll(service string) error {
// if service is empty, do nothing otherwise it might accidentally delete all secrets
if service == "" {
return ErrNotFound
}
creds, err := wincred.List()
if err != nil {
return err
}
prefix := k.credName(service, "")
deletedCount := 0
for _, cred := range creds {
if strings.HasPrefix(cred.TargetName, prefix) {
genericCred, err := wincred.GetGenericCredential(cred.TargetName)
if err != nil {
if err != syscall.ERROR_NOT_FOUND {
return err
}
} else {
err := genericCred.Delete()
if err != nil {
return err
}
deletedCount++
}
}
}
return nil
}
// credName combines service and username to a single string.
func (k windowsKeychain) credName(service, username string) string {
return service + ":" + username
}
func init() {
provider = windowsKeychain{}
}

View File

@@ -0,0 +1,257 @@
package ss
import (
"fmt"
"errors"
dbus "github.com/godbus/dbus/v5"
)
const (
serviceName = "org.freedesktop.secrets"
servicePath = "/org/freedesktop/secrets"
serviceInterface = "org.freedesktop.Secret.Service"
collectionInterface = "org.freedesktop.Secret.Collection"
collectionsInterface = "org.freedesktop.Secret.Service.Collections"
itemInterface = "org.freedesktop.Secret.Item"
sessionInterface = "org.freedesktop.Secret.Session"
promptInterface = "org.freedesktop.Secret.Prompt"
loginCollectionAlias = "/org/freedesktop/secrets/aliases/default"
collectionBasePath = "/org/freedesktop/secrets/collection/"
)
// Secret defines a org.freedesk.Secret.Item secret struct.
type Secret struct {
Session dbus.ObjectPath
Parameters []byte
Value []byte
ContentType string `dbus:"content_type"`
}
// NewSecret initializes a new Secret.
func NewSecret(session dbus.ObjectPath, secret string) Secret {
return Secret{
Session: session,
Parameters: []byte{},
Value: []byte(secret),
ContentType: "text/plain; charset=utf8",
}
}
// SecretService is an interface for the Secret Service dbus API.
type SecretService struct {
*dbus.Conn
object dbus.BusObject
}
// NewSecretService inializes a new SecretService object.
func NewSecretService() (*SecretService, error) {
conn, err := dbus.SessionBus()
if err != nil {
return nil, err
}
return &SecretService{
conn,
conn.Object(serviceName, servicePath),
}, nil
}
// OpenSession opens a secret service session.
func (s *SecretService) OpenSession() (dbus.BusObject, error) {
var disregard dbus.Variant
var sessionPath dbus.ObjectPath
err := s.object.Call(serviceInterface+".OpenSession", 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath)
if err != nil {
return nil, err
}
return s.Object(serviceName, sessionPath), nil
}
// CheckCollectionPath accepts dbus path and returns nil if the path is found
// in the collection interface (and can be used).
func (s *SecretService) CheckCollectionPath(path dbus.ObjectPath) error {
obj := s.Conn.Object(serviceName, servicePath)
val, err := obj.GetProperty(collectionsInterface)
if err != nil {
return err
}
paths := val.Value().([]dbus.ObjectPath)
for _, p := range paths {
if p == path {
return nil
}
}
return errors.New("path not found")
}
// GetCollection returns a collection from a name.
func (s *SecretService) GetCollection(name string) dbus.BusObject {
return s.Object(serviceName, dbus.ObjectPath(collectionBasePath+name))
}
// GetLoginCollection decides and returns the dbus collection to be used for login.
func (s *SecretService) GetLoginCollection() dbus.BusObject {
path := dbus.ObjectPath(collectionBasePath + "login")
if err := s.CheckCollectionPath(path); err != nil {
path = dbus.ObjectPath(loginCollectionAlias)
}
return s.Object(serviceName, path)
}
// Unlock unlocks a collection.
func (s *SecretService) Unlock(collection dbus.ObjectPath) error {
var unlocked []dbus.ObjectPath
var prompt dbus.ObjectPath
err := s.object.Call(serviceInterface+".Unlock", 0, []dbus.ObjectPath{collection}).Store(&unlocked, &prompt)
if err != nil {
return err
}
_, v, err := s.handlePrompt(prompt)
if err != nil {
return err
}
collections := v.Value()
switch c := collections.(type) {
case []dbus.ObjectPath:
unlocked = append(unlocked, c...)
}
if len(unlocked) != 1 || (collection != loginCollectionAlias && unlocked[0] != collection) {
return fmt.Errorf("failed to unlock correct collection '%v'", collection)
}
return nil
}
// Close closes a secret service dbus session.
func (s *SecretService) Close(session dbus.BusObject) error {
return session.Call(sessionInterface+".Close", 0).Err
}
// CreateCollection with the supplied label.
func (s *SecretService) CreateCollection(label string) (dbus.BusObject, error) {
properties := map[string]dbus.Variant{
collectionInterface + ".Label": dbus.MakeVariant(label),
}
var collection, prompt dbus.ObjectPath
err := s.object.Call(serviceInterface+".CreateCollection", 0, properties, "").
Store(&collection, &prompt)
if err != nil {
return nil, err
}
_, v, err := s.handlePrompt(prompt)
if err != nil {
return nil, err
}
if v.String() != "" {
collection = dbus.ObjectPath(v.String())
}
return s.Object(serviceName, collection), nil
}
// CreateItem creates an item in a collection, with label, attributes and a
// related secret.
func (s *SecretService) CreateItem(collection dbus.BusObject, label string, attributes map[string]string, secret Secret) error {
properties := map[string]dbus.Variant{
itemInterface + ".Label": dbus.MakeVariant(label),
itemInterface + ".Attributes": dbus.MakeVariant(attributes),
}
var item, prompt dbus.ObjectPath
err := collection.Call(collectionInterface+".CreateItem", 0,
properties, secret, true).Store(&item, &prompt)
if err != nil {
return err
}
_, _, err = s.handlePrompt(prompt)
if err != nil {
return err
}
return nil
}
// handlePrompt checks if a prompt should be handles and handles it by
// triggering the prompt and waiting for the Secret service daemon to display
// the prompt to the user.
func (s *SecretService) handlePrompt(prompt dbus.ObjectPath) (bool, dbus.Variant, error) {
if prompt != dbus.ObjectPath("/") {
err := s.AddMatchSignal(dbus.WithMatchObjectPath(prompt),
dbus.WithMatchInterface(promptInterface),
)
if err != nil {
return false, dbus.MakeVariant(""), err
}
defer func(s *SecretService, options ...dbus.MatchOption) {
_ = s.RemoveMatchSignal(options...)
}(s, dbus.WithMatchObjectPath(prompt), dbus.WithMatchInterface(promptInterface))
promptSignal := make(chan *dbus.Signal, 1)
s.Signal(promptSignal)
err = s.Object(serviceName, prompt).Call(promptInterface+".Prompt", 0, "").Err
if err != nil {
return false, dbus.MakeVariant(""), err
}
signal := <-promptSignal
switch signal.Name {
case promptInterface + ".Completed":
dismissed := signal.Body[0].(bool)
result := signal.Body[1].(dbus.Variant)
return dismissed, result, nil
}
}
return false, dbus.MakeVariant(""), nil
}
// SearchItems returns a list of items matching the search object.
func (s *SecretService) SearchItems(collection dbus.BusObject, search interface{}) ([]dbus.ObjectPath, error) {
var results []dbus.ObjectPath
err := collection.Call(collectionInterface+".SearchItems", 0, search).Store(&results)
if err != nil {
return nil, err
}
return results, nil
}
// GetSecret gets secret from an item in a given session.
func (s *SecretService) GetSecret(itemPath dbus.ObjectPath, session dbus.ObjectPath) (*Secret, error) {
var secret Secret
err := s.Object(serviceName, itemPath).Call(itemInterface+".GetSecret", 0, session).Store(&secret)
if err != nil {
return nil, err
}
return &secret, nil
}
// Delete deletes an item from the collection.
func (s *SecretService) Delete(itemPath dbus.ObjectPath) error {
var prompt dbus.ObjectPath
err := s.Object(serviceName, itemPath).Call(itemInterface+".Delete", 0).Store(&prompt)
if err != nil {
return err
}
_, _, err = s.handlePrompt(prompt)
if err != nil {
return err
}
return nil
}