mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-28 15:21:29 +05:30
Compare commits
195 Commits
v0.4.1
...
v0.7.0-beta.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f246b8570 | |||
| 0ab1dd03b3 | |||
| 3526ac5303 | |||
| 7c04fb1f9c | |||
| d4880decb1 | |||
| 214ecc1238 | |||
| 056f8792b7 | |||
| da1d534b46 | |||
| a2d8aac9dd | |||
| 48ab0d84e8 | |||
| e50298d973 | |||
| 2c31fa46db | |||
| a37d38a864 | |||
| 51249117fa | |||
| 3289c31a02 | |||
| ebd9dc6c79 | |||
| cc29c66bcc | |||
| dd465ef1f3 | |||
| a9d6be0a41 | |||
| becf2f2a95 | |||
| 6dfcfc15aa | |||
| 5a61a5bc61 | |||
| f522992580 | |||
| 9ec715d8ed | |||
| 9582eb6661 | |||
| e847de4b42 | |||
| 5302241995 | |||
| e5695df009 | |||
| acd90ea9da | |||
| 018b71b3c3 | |||
| 662759718c | |||
| fd0197473f | |||
| ab7fd12c67 | |||
| 6f1a1a10c1 | |||
| 667e4702b8 | |||
| 8fee1128b6 | |||
| 70f476531f | |||
| ebbaee6ed4 | |||
| e7e9118856 | |||
| 0e5dbc62ad | |||
| a251a3e4c5 | |||
| 4f1c080d3b | |||
| 17020d97fe | |||
| 2fc2313bb1 | |||
| e595cd6946 | |||
| a4e88fa4cf | |||
| 48bfed47cc | |||
| 6e2a858a7a | |||
| 8086f00d7d | |||
| 16c3805fc7 | |||
| fc9290adbc | |||
| 8369960895 | |||
| e4a625d24b | |||
| f552261f32 | |||
| 7a2694fc98 | |||
| 4b9fe97b79 | |||
| 0232333efa | |||
| 38bea2ce27 | |||
| c9a35c136e | |||
| 431b64c703 | |||
| 951b97d7f5 | |||
| f7d32862eb | |||
| 699f1d6790 | |||
| f4b8a8531e | |||
| f76f0b3f33 | |||
| f78081d1f7 | |||
| 4e7d4a98a4 | |||
| 43d1f2919f | |||
| 7b072d4d2e | |||
| c0c41f47b2 | |||
| 69fb197018 | |||
| 86b653a9f1 | |||
| 88f932172b | |||
| 54f0238b6b | |||
| 2bf42cfd47 | |||
| 1c9f7ede70 | |||
| 87c80bfba5 | |||
| 680ca48e6d | |||
| f6d4261609 | |||
| f755519945 | |||
| 11d9f9dba7 | |||
| beda055c57 | |||
| 22b8706efa | |||
| 8a9f93a99f | |||
| 063cca0a6d | |||
| 152d64310b | |||
| 77e30bd9dd | |||
| 74636a1592 | |||
| a3c1b5b757 | |||
| 6f4b0a7c06 | |||
| 1546024a83 | |||
| ecb01138bd | |||
| df92c83808 | |||
| 123ba1b7cd | |||
| 9c18f033f7 | |||
| d6c1f136e4 | |||
| 73e5f5dbfe | |||
| 5241b46610 | |||
| 3cba3913ea | |||
| 8502b53592 | |||
| 5da09fd251 | |||
| dd27c246bd | |||
| 26deb2937d | |||
| a13ff50017 | |||
| 1c295f7de8 | |||
| 093af814ee | |||
| d499cf5cb9 | |||
| b049338a6b | |||
| 91fc39d57a | |||
| 317219f3a6 | |||
| 21f4937ebc | |||
| 19b711cc92 | |||
| 1f635a73ad | |||
| 302e4ae938 | |||
| d68f9aeca3 | |||
| 05a9d4c50d | |||
| c2eec534ee | |||
| 5be80ca212 | |||
| f00de1b72f | |||
| 72f0375e37 | |||
| 3cce61446f | |||
| ec0d036273 | |||
| d3d9934897 | |||
| f212ea17df | |||
| 09e536a128 | |||
| 3c90bd29a4 | |||
| b8fb9690c8 | |||
| 7efaffb4c4 | |||
| 6338c06ba4 | |||
| 86c94d3e30 | |||
| 1ab1037707 | |||
| c69c0725bb | |||
| a8de4f6e8a | |||
| 4fbc86d7d0 | |||
| 2d19ebb7f4 | |||
| 054c53824f | |||
| be1808e3ed | |||
| e1d4c012bc | |||
| ad6ee5d58d | |||
| 94be56b6f4 | |||
| 37bf8afe8b | |||
| 3d7e1817f2 | |||
| 5651367281 | |||
| d8f57a18c6 | |||
| 4bc0bc2a85 | |||
| 167b137eb4 | |||
| 2a67ef1524 | |||
| 6ba4edf6e9 | |||
| fa4a4d4f71 | |||
| 4d60b372aa | |||
| 607bf3c174 | |||
| 74f4f75bc1 | |||
| 0e7779e5a1 | |||
| fb9549d396 | |||
| ed11c58ebf | |||
| 79ab7059b0 | |||
| 18d35e4984 | |||
| cdde659563 | |||
| 71779cf7f5 | |||
| 51c78cdc14 | |||
| 120e37f15d | |||
| 4962f775bf | |||
| ce617b3357 | |||
| f3071aff0a | |||
| 1515d4547e | |||
| f67589c36e | |||
| 479de2a4f5 | |||
| cdba2ced06 | |||
| 7f0538cd5e | |||
| 14381bf0d6 | |||
| 7133af577e | |||
| 2f2e85cfb9 | |||
| 47566329ff | |||
| b9d19b479a | |||
| e522e56699 | |||
| 8cb3338668 | |||
| cd76e4253f | |||
| 021d7d6a19 | |||
| 2a8670e0da | |||
| c5fbd3a5ad | |||
| c6cf8aa13b | |||
| cfe7556d6d | |||
| 23a1f310a5 | |||
| f7f2744e6d | |||
| 406812bf8e | |||
| 595b7f2531 | |||
| ad4463616e | |||
| 365da011fb | |||
| 1057cf7f86 | |||
| 40d06d0090 | |||
| de50f50ab0 | |||
| 3c3e1d3af1 | |||
| 19ec1c3164 | |||
| 2a3f7a4468 | |||
| 79f93e5e69 |
@@ -0,0 +1 @@
|
||||
/bin
|
||||
@@ -0,0 +1,182 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DESTDIR: ./bin
|
||||
GO_VERSION: 1.18.5
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- lint
|
||||
- validate-vendor
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
make ${{ matrix.target }}
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-20.04
|
||||
- macOS-11
|
||||
- windows-2022
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
-
|
||||
name: Install deps
|
||||
if: ${{ matrix.os == 'ubuntu-20.04' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dbus-x11 gnome-keyring libsecret-1-dev pass
|
||||
-
|
||||
name: GPG conf
|
||||
if: ${{ matrix.os == 'ubuntu-20.04' }}
|
||||
uses: actions/github-script@v6
|
||||
id: gpg
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const gnupgfolder = `${require('os').homedir()}/.gnupg`;
|
||||
if (!fs.existsSync(gnupgfolder)){
|
||||
fs.mkdirSync(gnupgfolder);
|
||||
}
|
||||
fs.copyFile('.github/workflows/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
core.setOutput('key', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.key', {encoding: 'utf8'}));
|
||||
core.setOutput('passphrase', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.pass', {encoding: 'utf8'}));
|
||||
-
|
||||
name: Import GPG key
|
||||
if: ${{ matrix.os == 'ubuntu-20.04' }}
|
||||
uses: crazy-max/ghaction-import-gpg@v5
|
||||
with:
|
||||
gpg_private_key: ${{ steps.gpg.outputs.key }}
|
||||
passphrase: ${{ steps.gpg.outputs.passphrase }}
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" = "ubuntu-20.04" ]; then
|
||||
echo -e "trust\n5\ny" | gpg --batch --no-tty --command-fd 0 --edit-key 7D851EB72D73BDA0
|
||||
pass init 7D851EB72D73BDA0
|
||||
fi
|
||||
go test -short -v -coverprofile=./coverage.txt -covermode=atomic ./...
|
||||
go tool cover -func=./coverage.txt
|
||||
shell: bash
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.txt
|
||||
|
||||
test-sandboxed:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=test
|
||||
*.cache-to=type=gha,scope=test,mode=max
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ${{ env.DESTDIR }}//coverage.txt
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Build
|
||||
run: |
|
||||
make release
|
||||
env:
|
||||
CACHE_FROM: type=gha,scope=build
|
||||
CACHE_TO: type=gha,scope=build,mode=max
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh ${{ env.DESTDIR }}
|
||||
-
|
||||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-credential-helpers
|
||||
path: ${{ env.DESTDIR }}/*
|
||||
if-no-files-found: error
|
||||
-
|
||||
name: GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ env.DESTDIR }}/*
|
||||
|
||||
build-deb:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Build
|
||||
run: |
|
||||
make deb
|
||||
@@ -0,0 +1,106 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
lQdGBF6tzaABEACjFbX7PFEG6vDPN2MPyxYW7/3o/sonORj4HXUFjFxxJxktJ3x3
|
||||
N1ayHPJ1lqIeoiY7jVbq0ZdEVGkd3YsKG9ZMdZkzGzY6PQPC/+M8OnzOiOPwUdWc
|
||||
+Tdhh115LvVz0MMKYiab6Sn9cgxj9On3LCQKpjvMDpPo9Ttf6v2GQIw8h2ACvdzQ
|
||||
71LtIELS/I+dLbfZiwpUu2fhQT13EJkEnYMOYwM5jNUd66P9itUc7MrOWjkicrKP
|
||||
oF1dQaCM+tuKuxvD8WLdiwU5x60NoGkJHHUehKQXl2dVzjpqEqHKEBJt9tfJ9lpE
|
||||
YIisgwB8o3pes0fgCehjW2zI95/o9+ayJ6nl4g5+mSvWRXEu66h71nwM0Yuvquk8
|
||||
3me7qhYfDrDdCwcxS5BS1hwakTgUQLD99FZjbx1j8sq96I65O0GRdyU2PR8KIjwu
|
||||
JrkTH4ZlKxK3FQghUhFoA5GkiDb+eClmRMSni5qg+81T4XChmUkEprA3eWCHL+Ma
|
||||
xRNNxLS+r6hH9HG5JBxpV3iaTI9HHpnQKhEeaLXqsUTDZliN9hP7Ywo8bpUB8j2d
|
||||
oWYwDV4dPyMKr6Fb8RDCh2q5gJGbVp8w/NmmBTeL+IP2fFggJkRfyumv3Ul7x66L
|
||||
tBFQ4rYo4JUUrGweSTneG6REIgxH66hIrNl6Vo/D1ZyknTe1dMOu/BTkkQARAQAB
|
||||
/gcDAqra8KO+h3bfyu90vxTL1ro4x/x9il7VBcWlIR4cBP7Imgxv+T4hwPIu8P1x
|
||||
lOlxLNWegFOV0idoTy1o3VLLBev/F+IlspX4A+2XEIddR6nZnKFi0Lv2L4TKgE9E
|
||||
VJJTszmviDIRLMLN9dWzDfA8hj5tR5Inot92CHRF414AS22JHvlhbFSLQnjqsN+C
|
||||
n1cQpNOJhkxsSfZsxjnFa/70y/u8v0o8mzyLZmk9HpzRHGzoz8IfpLp8OTqBR9u6
|
||||
zzoKLy16zZO55OKbj7h8uVZvDUq9l8iDICpqWMdZqBJIl56MBexYKgYxh3YO/8v2
|
||||
oXli+8Xuaq5QLiCN3yT7IbKoYzplnFfaJwFiMh7R1iPLXaYAZ0qdRijlbtseTK1m
|
||||
oHNkwUbxVzjkh4LfE8UpmMwZn5ZjWni3230SoiXuKy0OHkGvwGvWWAL1mEuoYuUI
|
||||
mFMcH5MnixP8oQYZKDj2IR/yEeOpdU6B/tr3Tk1NidLf7pUMqG7Ff1NU6dAUeBpa
|
||||
9xahITMjHvrhgMISY4IYZep5cEnVw8lQTpUJtW/ePMzrFhu3sA7oNdj9joW/VMfz
|
||||
H7MHwwavtICsYqoqV3lnjX4EC9dW6o8PTUg2u956dmtK7KAyUK/+w2aLNGT28ChN
|
||||
jhRYHvHzB9Kw5asqI/lTM49eqslBqYQMTTjdBphkYuSZQzNMf291j/ZmoLhD1A1a
|
||||
S8tUnNygKV4D1cJYgSXfzhFoU8ib/0SPo+KqQ+CzGS+wxXg6WNBA6wepTjpnVVx3
|
||||
4JADP8IJcDC3P0iwAreWjSy15F1cvemFFB0SLNUkyZGzsxtKzbM1+8khl68+eazC
|
||||
LzRj0rxfIF5znWjX1QFhKxCk6eF0IWDY0+b3DBkmChME9YDXJ3TthcqA7JgcX4JI
|
||||
M4/wdqhgerJYOmj+i2Q0M+Bu02icOJYMwTMMsDVl7XGHkaCuRgZ54eZAUH7JFwUm
|
||||
1Ct3tcaqiTMmz0ngHVqBTauzgqKDvzwdVqdfg05H364nJMay/3omR6GayIb5CwSo
|
||||
xdNVwG3myPPradT9MP09mDr4ys2zcnQmCkvTVBF6cMZ1Eh6PQQ8CyQWv0zkaBnqj
|
||||
JrM1hRpgW4ZlRosSIjCaaJjolN5QDcXBM9TbW9ww+ZYstazN2bV1ZQ7BEjlHQPa1
|
||||
BhzMsvqkbETHsIpDNF52gZKn3Q9eIX05BeadzpHUb5/XOheIHVIdhSaTlgl/qQW5
|
||||
hQgPGSzSV6KhXEY7aevTdvOgq++WiELkjfz2f2lQFesTjFoQWEvxVDUmLxHtEhaN
|
||||
DOuh4H3mX5Opn3pLQmqWVhJTbFdx+g5qQd0NCW4mDaTFWTRLFLZQsSJxDSeg9xrY
|
||||
gmaii8NhMZRwquADW+6iU6KfraBhngi7HRz4TfqPr9ma/KUY464cqim1fnwXejyx
|
||||
jsb5YHR9R66i+F6P/ysF5w+QuVdDt1fnf9GLay0r6qxpA8ft2vGPcDs4806Huj+7
|
||||
Aq5VeJaNkCuh3GR3xVnCFAz/7AtkO6xKuZm8B3q904UuMdSmkhWbaobIuF/B2B6S
|
||||
eawIXQHEOplK3ic26d8Ckf4gbjeORfELcMAEi5nGXpTThCdmxQApCLxAYYnTfQT1
|
||||
xhlDwT9xPEabo98mIwJJsAU5VsTDYW+qfo4qIx8gYoSKc9Xu3yVh3n+9k43Gcm5V
|
||||
9lvK1slijf+TzODZt/jsmkF8mPjXyP5KOI+xQp/m4PxW3pp57YrYj/Rnwga+8DKX
|
||||
jMsW7mLAAZ/e+PY6z/s3x1Krfk+Bb5Ph4mI0zjw5weQdtyEToRgveda0GEpvZSBU
|
||||
ZXN0ZXIgPGpvZUBmb28uYmFyPokCNgQQAQgAIAUCXq3NoAYLCQcIAwIEFQgKAgQW
|
||||
AgEAAhkBAhsDAh4BAAoJEH2FHrctc72gxtQP/AulaClIcn/kDt43mhYnyLglPfbo
|
||||
AqPlU26chXolBg0Wo0frFY3aIs5SrcWEf8aR4XLwCFGyi3vya0CUxjghN5tZBYqo
|
||||
vswbT00zP3ohxxlJFCRRR9bc7OZXCgTddtfVf6EKrUAzIkbWyAhaJnwJy/1UGpSw
|
||||
SEO/KpastrVKf3sv1wqOeFQ4DFyjaNda+xv3dVWS8db7KogqJiPFZXrQK3FKVIxS
|
||||
fxRSmKaYN7//d+xwVAEY++RrnL/o8B2kV6N68cCpQWJELyYnJzis9LBcWd/3wiYh
|
||||
efTyY+ePKUjcB+kEZnyJfLc7C2hll2e7UJ0fxv+k8vHReRhrNWmGRXsjNRxiw3U0
|
||||
hfvxD/C8nyqAbeTHp4XDX78Tc3XCysAqIYboIL+RyewDMjjLj5vzUYAdUdtyNaD7
|
||||
C6M2R6pN1GAt52CJmC/Z6F7W7GFGoYOdEkVdMQDsjCwScyEUNlGj9Zagw5M2EgSe
|
||||
6gaHgMgTzsMzCc4W6WV5RcS55cfDNOXtxPsMJTt4FmXrjl11prBzpMfpU5a9zxDZ
|
||||
oi54ZZ8VPE6jsT4Lzw3sni3c83wm28ArM20AzZ1vh7fk3Sfd0u4Yaz7s9JlEm5+D
|
||||
34tEyli28+QjCQc18EfQUiJqiYEJRxJXJ3esvMHfYi45pV/Eh5DgRW1305fUJV/6
|
||||
+rGpg0NejsHoZdZPnQdGBF6tzaABEAC4mVXTkVk6Kdfa4r5zlzsoIrR27laUlMkb
|
||||
OBMt+aokqS+BEbmTnMg6xIAmcUT5uvGAc8S/WhrPoYfc15fTUyHIz8ZbDoAg0LO6
|
||||
0Io4VkAvNJNEnsSV9VdLBh/XYlc4K49JqKyWTL4/FJFAGbsmHY3b+QU90AS6FYRv
|
||||
KeBAoiyebrjx0vmzb8E8h3xthVLN+AfMlR1ickY62zvnpkbncSMY/skur1D2KfbF
|
||||
3sFprty2pEtjFcyB5+18l2IyyHGOlEUw1PZdOAV4/Myh1EZRgYBPs80lYTJALCVF
|
||||
IdOakH33WJCImtNZB0AbDTABG+JtMjQGscOa0qzf1Y/7tlhgCrynBBdaIJTx95TD
|
||||
21BUHcHOu5yTIS6Ulysxfkv611+BiOKHgdq7DVGP78VuzA7bCjlP1+vHqIt3cnIa
|
||||
t2tEyuZ/XF4uc3/i4g0uP9r7AmtET7Z6SKECWjpVv+UEgLx5Cv+ql+LSKYQMvU9a
|
||||
i3B1F9fatn3FSLVYrL4aRxu4TSw9POb0/lgDNmN3lGQOsjGCZPibkHjgPEVxKuiq
|
||||
9Oi38/VTQ0ZKAmHwBTq1WTZIrPrCW0/YMQ6yIJZulwQ9Yx1cgzYzEfg04fPXlXMi
|
||||
vkvNpKbYIICzqj0/DVztz9wgpW6mnd0A2VX2dqbMM0fJUCHA6pj8AvXY4R+9Q4rj
|
||||
eWRK9ycInQARAQAB/gcDApjt7biRO0PEyrrAiUwDMsJL4/CVMu11qUWEPjKe2Grh
|
||||
ZTW3N+m3neKPRULu+LUtndUcEdVWUCoDzAJ7MwihZtV5vKST/5Scd2inonOaJqoA
|
||||
nS3wnEMN/Sc93HAZiZnFx3NKjQVNCwbuEs45mXkkcjLm2iadrTL8fL4acsu5IsvD
|
||||
LbDwVOPeNnHKl6Hr20e39fK0FuJEyH49JM6U3B1/8385sJB8+E24+hvSF81aMddh
|
||||
Ne4Bc3ZYiYaKxe1quPNKC0CQhAZiT7LsMfkInXr0hY1I+kISNXEJ1dPYOEWiv0Ze
|
||||
jD5Pupn34okKNEeBCx+dK8BmUCi6Jgs7McUA7hN0D/YUS++5fuR55UQq2j8Ui0tS
|
||||
P8GDr86upH3PgEL0STh9fYfJ7TesxurwonWjlmmT62Myl4Pr+RmpS6PXOnhtcADm
|
||||
eGLpzhTveFj4JBLMpyYHgBTqcs12zfprATOpsI/89kmQoGCZpG6+AbfSHqNNPdy2
|
||||
eqUCBhOZlIIda1z/cexmU3f/gBqyflFf8fkvmlO4AvI8aMH3OpgHdWnzh+AB51xj
|
||||
kmdD/oWel9v7Dz4HoZUfwFaLZ0fE3P9voD8e+sCwqQwVqRY4L/BOYPD5noVOKgOj
|
||||
ABNKu5uKrobj6rFUi6DTUCjFGcmoF1Sc06xFNaagUNggRbmlC/dz22RWdDUYv5ra
|
||||
N6TxIDkGC0cK6ujyK0nes3DN0aHjgwWuMXDYkN3UckiebI4Cv/eF9jvUKOSiIcy1
|
||||
RtxdazZS4dYg2LBMeJKVkPi5elsNyw2812nEY3du/nEkQYXfYgWOF27OR+g4Y9Yw
|
||||
1BiqJ1TTjbQnd/khOCrrbzDH1mw00+1XVsT6wjObuYqqxPPS87UrqmMf6OdoYfPm
|
||||
zEOnNLBnsJ5VQM3A3pcT40RfdBrZRO8LjGhzKTreyq3C+jz0RLa5HNE8GgOhGyck
|
||||
ME4h+RhXlE8KGM+tTo6PA1NJSrEt+8kZzxjP4rIEn0aVthCkNXK12inuXtnHm0ao
|
||||
iLUlQOsfPFEnzl0TUPd7+z7j/wB+XiKU/AyEUuB0mvdxdKtqXvajahOyhLjzHQhz
|
||||
ZnNlgANGtiqcSoJmkJ8yAvhrtQX51fQLftxbArRW1RYk/5l+Gy3azR+gUC17M6JN
|
||||
jrUYxn0zlAxDGFH7gACHUONwVekcuEffHzgu2lk7MyO1Y+lPnwabqjG0eWWHuU00
|
||||
hskJlXyhj7DeR12bwjYkyyjG62GvOH02g3OMvUgNGH+K321Dz539csCh/xwtg7Wt
|
||||
U3YAphU7htQ1dPDfk1IRs7DQo2L+ZTE57vmL5m0l6fTataEWBPUXkygfQFUJOM6Q
|
||||
yY76UEZww1OSDujNeY171NSTzXCVkUeAdAMXgjaHXWLK2QUQUoXbYX/Kr7Vvt9Fu
|
||||
Jh6eGjjp7dSjQ9+DW8CAB8vxd93gsQQGWYjmGu8khkEmx6OdZhmSbDbe915LQTb9
|
||||
sPhk2s5/Szsvr5W2JJ2321JI6KXBJMZvPC5jEBWmRzOYkRd2vloft+CSMfXF+Zfd
|
||||
nYtc6R3dvb9vcjo+a9wFtfcoDsO0MaPSM+9GB25MamdatmGX6iLOy9Re1UABwUi/
|
||||
VhTWNkP5uzqx0sDwHEIa2rYOwxpIZDwwjM3oOASCW1DDBQ0BI9KNjfIeL3ubx2mS
|
||||
2x8hFU9qSK4umoDNbzOqGPSlkdbiPcNjF2ZcSN1qQZiYdwLL5dw6APNyBVjxTN1J
|
||||
gkCdJ/HwAY+r93Lbl5g8gz8d0vJEyfn//34sn9u+toSTw55GcG9Ks1kSKIeDNh0h
|
||||
MiPm3HmJAh8EGAEIAAkFAl6tzaACGwwACgkQfYUety1zvaBV9hAAgliX36pXJ59g
|
||||
3I9/4R68e/fGg0FMM6D+01yCeiKApOYRrJ0cYKn7ITDYmHhlGGpBAie90UsqX12h
|
||||
hdLP7LoQx7sjTyzQt6JmpA8krIwi2ON7FKBkdYb8IYx4mE/5vKnYT4/SFnwTmnZY
|
||||
+m+NzK2U/qmhq8JyO8gozdAKJUcgz49IVv2Ij0tQ4qaPbyPwQxIDyKnT758nJhB1
|
||||
jTqo+oWtER8q3okzIlqcArqn5rDaNJx+DRYL4E/IddyHQAiUWUka8usIUqeW5reu
|
||||
zoPUE2CCfOJSGArkqHQQqMx0WEzjQTwAPaHrQbera4SbiV/o4CLCV/u5p1Qnig+Q
|
||||
iUsakmlD299t//125LIQEa5qzd9hRC7u1uJS7VdW8eGIEcZ0/XT/sr+z23z0kpZH
|
||||
D3dXPX0BwM4IP9xu31CNg10x0rKwjbxy8VaskFEelpqpu+gpAnxqMd1evpeUHcOd
|
||||
r5RgPgkNFfba9Nbxf7uEX+HOmsOM+kdtSmdGIvsBZjVnW31nnoDMp49jG4OynjrH
|
||||
cRuoM9sxdr6UDqb22CZ3/e0YN4UaZM3YDWMVaP/QBVgvIFcdByqNWezpd9T4ZUII
|
||||
MZlaV1uRnHg6B/zTzhIdMM80AXz6Uv6kw4S+Lt7HlbrnMT7uKLuvzH7cle0hcIUa
|
||||
PejgXO0uIRolYQ3sz2tMGhx1MfBqH64=
|
||||
=WbwB
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
@@ -0,0 +1 @@
|
||||
with stupid passphrase
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
set -ex
|
||||
|
||||
gpg --batch --gen-key <<-EOF
|
||||
%echo Generating a standard key
|
||||
Key-Type: DSA
|
||||
Key-Length: 1024
|
||||
Subkey-Type: ELG-E
|
||||
Subkey-Length: 1024
|
||||
Name-Real: Meshuggah Rocks
|
||||
Name-Email: meshuggah@example.com
|
||||
Expire-Date: 0
|
||||
# Do a commit here, so that we can later print "done" :-)
|
||||
%commit
|
||||
%echo done
|
||||
EOF
|
||||
@@ -0,0 +1,3 @@
|
||||
default-cache-ttl 21600
|
||||
max-cache-ttl 31536000
|
||||
allow-preset-passphrase
|
||||
@@ -0,0 +1,71 @@
|
||||
################################################################################
|
||||
# GnuPG Options
|
||||
|
||||
# (OpenPGP-Configuration-Options)
|
||||
# Assume that command line arguments are given as UTF8 strings.
|
||||
utf8-strings
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Set the list of personal digest/cipher/compression preferences. This allows
|
||||
# the user to safely override the algorithm chosen by the recipient key
|
||||
# preferences, as GPG will only select an algorithm that is usable by all
|
||||
# recipients.
|
||||
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
|
||||
personal-cipher-preferences AES256 AES192 AES CAST5 CAMELLIA192 BLOWFISH TWOFISH CAMELLIA128 3DES
|
||||
personal-compress-preferences ZLIB BZIP2 ZIP
|
||||
|
||||
# Set the list of default preferences to string. This preference list is used
|
||||
# for new keys and becomes the default for "setpref" in the edit menu.
|
||||
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
|
||||
|
||||
# (OpenPGP-Esoteric-Options)
|
||||
# Use name as the message digest algorithm used when signing a key. Running the
|
||||
# program with the command --version yields a list of supported algorithms. Be
|
||||
# aware that if you choose an algorithm that GnuPG supports but other OpenPGP
|
||||
# implementations do not, then some users will not be able to use the key
|
||||
# signatures you make, or quite possibly your entire key.
|
||||
#
|
||||
# SHA-1 is the only algorithm specified for OpenPGP V4. By changing the
|
||||
# cert-digest-algo, the OpenPGP V4 specification is not met but with even
|
||||
# GnuPG 1.4.10 (release 2009) supporting SHA-2 algorithm, this should be safe.
|
||||
# Source: https://tools.ietf.org/html/rfc4880#section-12.2
|
||||
cert-digest-algo SHA512
|
||||
digest-algo SHA256
|
||||
|
||||
# Selects how passphrases for symmetric encryption are mangled. 3 (the default)
|
||||
# iterates the whole process a number of times (see --s2k-count).
|
||||
s2k-mode 3
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Use name as the cipher algorithm for symmetric encryption with a passphrase
|
||||
# if --personal-cipher-preferences and --cipher-algo are not given. The
|
||||
# default is AES-128.
|
||||
s2k-cipher-algo AES256
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Use name as the digest algorithm used to mangle the passphrases for symmetric
|
||||
# encryption. The default is SHA-1.
|
||||
s2k-digest-algo SHA512
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Specify how many times the passphrases mangling for symmetric encryption is
|
||||
# repeated. This value may range between 1024 and 65011712 inclusive. The
|
||||
# default is inquired from gpg-agent. Note that not all values in the
|
||||
# 1024-65011712 range are legal and if an illegal value is selected, GnuPG will
|
||||
# round up to the nearest legal value. This option is only meaningful if
|
||||
# --s2k-mode is set to the default of 3.
|
||||
s2k-count 1015808
|
||||
|
||||
################################################################################
|
||||
# GnuPG View Options
|
||||
|
||||
# Select how to display key IDs. "long" is the more accurate (but less
|
||||
# convenient) 16-character key ID. Add an "0x" to include an "0x" at the
|
||||
# beginning of the key ID.
|
||||
keyid-format 0xlong
|
||||
|
||||
# List all keys with their fingerprints. This is the same output as --list-keys
|
||||
# but with the additional output of a line with the fingerprint. If this
|
||||
# command is given twice, the fingerprints of all secondary keys are listed too.
|
||||
with-fingerprint
|
||||
with-fingerprint
|
||||
+1
-1
@@ -1 +1 @@
|
||||
bin
|
||||
/bin
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
modules-download-mode: vendor
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gofmt
|
||||
- govet
|
||||
- deadcode
|
||||
- depguard
|
||||
- goimports
|
||||
- ineffassign
|
||||
- misspell
|
||||
- unused
|
||||
- varcheck
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- structcheck
|
||||
disable-all: true
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
include-go-root: true
|
||||
packages:
|
||||
# The io/ioutil package has been deprecated.
|
||||
# https://go.dev/doc/go1.16#ioutil
|
||||
- io/ioutil
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "stutters"
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
---
|
||||
# See appveyor.yml for windows build.
|
||||
sudo: required
|
||||
language: go
|
||||
dist: trusty
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
notifications:
|
||||
email: false
|
||||
go:
|
||||
- 1.6
|
||||
install: make deps
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libsecret-1-dev
|
||||
before_script:
|
||||
- "export DISPLAY=:99.0"
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh ci/before_script_linux.sh; fi
|
||||
- make validate
|
||||
script: make test
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "cGs5cao/MeVQVnum+Pr/Tpv+w83NsqGVS3wxvi3LYEf2ON4Kkmtd+Alwi0YFkGPJmSY0jZOct8NVK/M70qSnIU4l+AAq9+3KSMv23u4xrmy2sQog3AF+Ve3Rac+iYwZHOWwGs9I67CSuVv0vjJNVsDsTVefc25lHJImjRvXIS4p9xYzRPeUDCoqAo/QMVE+vFiMyxydsvt8fhd0gZCjPYWEpyHe9tjZ1tr1HsHZKFAjVb6AmF45d8rvadPoVUuLaOtr35wDC3XRKEvCZUefQpwLkrNj7j2L1rVGlY1xTE2APpLtvfd7R1Mx6kSfS1Gm3Pwcv3mugadXIhecL0lsdnU+BANjX3VUiv4ryzTPbsge966mv9ZQYwAzgCQTWRtMNJqsAnPZTeAkiOntd+HMQbPpxljOxv1sjDPY+EIZesyB3yQRJI8vMxqFcAjxeRyLcBqEnRFC2nd/Ln0KZ7ZFu16FcpNqRojdBayyypuXKqAiBNwtp4ti/65x8eHfBJuNjJtNZkRsJEYam4CYMRLxds9plKQfkaZ8045PKpyXO8fMpUhrfqSVID4IrYvD+io6XoXtdR4Lk6isZ2EgrjdrqgdG70S5lwKihL4iAi2F2ZCWhngFhkeNVOZunEWE6qZMk5wKODajR9sixGDApGPZQVojHwCNRGILZaHZ39JCIj3s="
|
||||
# upload file artifacts using a glob expression.
|
||||
# It requires both options `file_glob` and `file`:
|
||||
# https://github.com/travis-ci/dpl/blob/master/lib/dpl/provider/releases.rb#L47-L53
|
||||
file_glob: true
|
||||
file: docker-credential-*-${TRAVIS_TAG}-amd64.tar.gz
|
||||
# don't delete the artifacts from previous phases
|
||||
skip_cleanup: true
|
||||
# deploy when a new tag is pushed
|
||||
on:
|
||||
tags: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
# Pushes and PR to the master branch
|
||||
- master
|
||||
# IMPORTANT Ruby regex to match tags. Required, or travis won't trigger deploys when a new tag
|
||||
# is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
@@ -1,27 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This changelog tracks the releases of docker-credential-helpers.
|
||||
This project includes different binaries per platform.
|
||||
The platform released is identified after the tag name.
|
||||
|
||||
## v0.4.0 (Go client, Mac OS X, Windows, Linux)
|
||||
|
||||
- Full implementation for OSX ready
|
||||
- Fix some windows issues
|
||||
- Implement client.List, change list API
|
||||
- mac: delete credentials before adding them to avoid already exist error (fixes #37)
|
||||
|
||||
|
||||
## v0.3.0 (Go client)
|
||||
|
||||
- Add Go client library to talk with the native programs.
|
||||
|
||||
## v0.2.0 (Mac OS X, Windows, Linux)
|
||||
|
||||
- Initial release of docker-credential-secretservice for Linux.
|
||||
- Use new secrets payload introduced in https://github.com/docker/docker/pull/20970.
|
||||
|
||||
## v0.1.0 (Mac OS X, Windows)
|
||||
|
||||
- Initial release of docker-credential-osxkeychain for Mac OS X.
|
||||
- Initial release of docker-credential-wincred for Microsoft Windows.
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.18.5
|
||||
ARG XX_VERSION=1.1.2
|
||||
ARG OSXCROSS_VERSION=11.3-r7-alpine
|
||||
ARG GOLANGCI_LINT_VERSION=v1.47.3
|
||||
|
||||
ARG PACKAGE=github.com/docker/docker-credential-helpers
|
||||
|
||||
# xx is a helper for cross-compilation
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
# osxcross contains the MacOSX cross toolchain for xx
|
||||
FROM crazymax/osxcross:${OSXCROSS_VERSION} AS osxcross
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS gobase
|
||||
COPY --from=xx / /
|
||||
RUN apk add --no-cache clang file git lld llvm make pkgconf rsync
|
||||
ENV GOFLAGS="-mod=vendor"
|
||||
ENV CGO_ENABLED="1"
|
||||
WORKDIR /src
|
||||
|
||||
FROM gobase AS vendored
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs \
|
||||
--mount=target=/go/pkg/mod,type=cache <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
mkdir /out
|
||||
cp -r go.mod go.sum vendor /out
|
||||
EOT
|
||||
|
||||
FROM scratch AS vendor-update
|
||||
COPY --from=vendored /out /
|
||||
|
||||
FROM vendored AS vendor-validate
|
||||
RUN --mount=type=bind,target=.,rw <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
git add -A
|
||||
rm -rf vendor
|
||||
cp -rf /out/* .
|
||||
if [ -n "$(git status --porcelain -- go.mod go.sum vendor)" ]; then
|
||||
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "make vendor"'
|
||||
git status --porcelain -- go.mod go.sum vendor
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
||||
FROM gobase AS lint
|
||||
RUN apk add musl-dev gcc libsecret-dev pass
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
|
||||
golangci-lint run ./...
|
||||
|
||||
FROM gobase AS base
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apk add musl-dev gcc libsecret-dev pass
|
||||
|
||||
FROM base AS test
|
||||
RUN xx-apk add gnome-keyring gpg-agent gnupg-gpgconf pass
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||
set -e
|
||||
cp -r .github/workflows/fixtures /root/.gnupg
|
||||
gpg-connect-agent "RELOADAGENT" /bye
|
||||
gpg --import --batch --yes /root/.gnupg/7D851EB72D73BDA0.key
|
||||
echo -e "trust\n5\ny" | gpg --batch --no-tty --command-fd 0 --edit-key 7D851EB72D73BDA0
|
||||
gpg-connect-agent "PRESET_PASSPHRASE 3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627 -1 77697468207374757069642070617373706872617365" /bye
|
||||
gpg-connect-agent "KEYINFO 3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627" /bye
|
||||
gpg-connect-agent "PRESET_PASSPHRASE BA83FC8947213477F28ADC019F6564A956456163 -1 77697468207374757069642070617373706872617365" /bye
|
||||
gpg-connect-agent "KEYINFO BA83FC8947213477F28ADC019F6564A956456163" /bye
|
||||
pass init 7D851EB72D73BDA0
|
||||
|
||||
mkdir /out
|
||||
xx-go test -short -v -coverprofile=/out/coverage.txt -covermode=atomic ./...
|
||||
xx-go tool cover -func=/out/coverage.txt
|
||||
EOT
|
||||
|
||||
FROM scratch AS test-coverage
|
||||
COPY --from=test /out /
|
||||
|
||||
FROM gobase AS version
|
||||
RUN --mount=target=. \
|
||||
echo -n "$(./hack/git-meta version)" | tee /tmp/.version ; echo -n "$(./hack/git-meta revision)" | tee /tmp/.revision
|
||||
|
||||
FROM base AS build-linux
|
||||
ARG PACKAGE
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
|
||||
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
|
||||
set -ex
|
||||
xx-go --wrap
|
||||
make build-pass build-secretservice PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
|
||||
xx-verify /out/docker-credential-pass
|
||||
xx-verify /out/docker-credential-secretservice
|
||||
EOT
|
||||
|
||||
FROM base AS build-darwin
|
||||
ARG PACKAGE
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,from=osxcross,src=/osxsdk,target=/xx-sdk \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
|
||||
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
|
||||
set -ex
|
||||
xx-go --wrap
|
||||
go install std
|
||||
make build-osxkeychain build-pass PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
|
||||
xx-verify /out/docker-credential-osxkeychain
|
||||
xx-verify /out/docker-credential-pass
|
||||
EOT
|
||||
|
||||
FROM base AS build-windows
|
||||
ARG PACKAGE
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
|
||||
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
|
||||
set -ex
|
||||
xx-go --wrap
|
||||
make build-wincred PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
|
||||
mv /out/docker-credential-wincred /out/docker-credential-wincred.exe
|
||||
xx-verify /out/docker-credential-wincred.exe
|
||||
EOT
|
||||
|
||||
FROM build-$TARGETOS AS build
|
||||
|
||||
FROM scratch AS binaries
|
||||
COPY --from=build /out /
|
||||
|
||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||
WORKDIR /work
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
RUN --mount=from=binaries \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version <<EOT
|
||||
set -e
|
||||
mkdir /out
|
||||
version="$(cat /tmp/.version)"
|
||||
[ "$TARGETOS" = "windows" ] && ext=".exe"
|
||||
for f in *; do
|
||||
cp "$f" "/out/${f%.*}-${version}.${TARGETOS}-${TARGETARCH}${TARGETVARIANT}${ext}"
|
||||
done
|
||||
EOT
|
||||
|
||||
FROM scratch AS release
|
||||
COPY --from=releaser /out/ /
|
||||
|
||||
FROM binaries
|
||||
+6
-30
@@ -11,20 +11,16 @@
|
||||
[Org]
|
||||
[Org."Core maintainers"]
|
||||
people = [
|
||||
"aaronlehmann",
|
||||
"calavera",
|
||||
"coolljt0725",
|
||||
"cpuguy83",
|
||||
"crosbymichael",
|
||||
"dnephin",
|
||||
"dongluochen",
|
||||
"duglin",
|
||||
"estesp",
|
||||
"icecrime",
|
||||
"jhowardmsft",
|
||||
"lk4d4",
|
||||
"mavenugo",
|
||||
"mhbauer",
|
||||
"n4ss",
|
||||
"runcom",
|
||||
"stevvooe",
|
||||
"thajeztah",
|
||||
@@ -44,16 +40,6 @@
|
||||
|
||||
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||
|
||||
[people.aaronlehmann]
|
||||
Name = "Aaron Lehmann"
|
||||
Email = "aaron.lehmann@docker.com"
|
||||
GitHub = "aaronlehmann"
|
||||
|
||||
[people.calavera]
|
||||
Name = "David Calavera"
|
||||
Email = "david.calavera@gmail.com"
|
||||
GitHub = "calavera"
|
||||
|
||||
[people.coolljt0725]
|
||||
Name = "Lei Jitang"
|
||||
Email = "leijitang@huawei.com"
|
||||
@@ -74,11 +60,6 @@
|
||||
Email = "dnephin@gmail.com"
|
||||
GitHub = "dnephin"
|
||||
|
||||
[people.dongluochen]
|
||||
Name = "Dongluo Chen"
|
||||
Email = "dongluo.chen@docker.com"
|
||||
GitHub = "dongluochen"
|
||||
|
||||
[people.duglin]
|
||||
Name = "Doug Davis"
|
||||
Email = "dug@us.ibm.com"
|
||||
@@ -89,21 +70,11 @@
|
||||
Email = "estesp@linux.vnet.ibm.com"
|
||||
GitHub = "estesp"
|
||||
|
||||
[people.icecrime]
|
||||
Name = "Arnaud Porterie"
|
||||
Email = "arnaud@docker.com"
|
||||
GitHub = "icecrime"
|
||||
|
||||
[people.jhowardmsft]
|
||||
Name = "John Howard"
|
||||
Email = "jhoward@microsoft.com"
|
||||
GitHub = "jhowardmsft"
|
||||
|
||||
[people.lk4d4]
|
||||
Name = "Alexander Morozov"
|
||||
Email = "lk4d4@docker.com"
|
||||
GitHub = "lk4d4"
|
||||
|
||||
[people.mavenugo]
|
||||
Name = "Madhu Venugopal"
|
||||
Email = "madhu@docker.com"
|
||||
@@ -114,6 +85,11 @@
|
||||
Email = "mbauer@us.ibm.com"
|
||||
GitHub = "mhbauer"
|
||||
|
||||
[people.n4ss]
|
||||
Name = "Nassim Eddequiouaq"
|
||||
Email = "eddequiouaq.nassim@gmail.com"
|
||||
GitHub = "n4ss"
|
||||
|
||||
[people.runcom]
|
||||
Name = "Antonio Murdaca"
|
||||
Email = "runcom@redhat.com"
|
||||
|
||||
@@ -1,39 +1,74 @@
|
||||
.PHONY: all deps osxkeychain secretservice test validate wincred
|
||||
PACKAGE ?= github.com/docker/docker-credential-helpers
|
||||
VERSION ?= $(shell ./hack/git-meta version)
|
||||
REVISION ?= $(shell ./hack/git-meta revision)
|
||||
|
||||
TRAVIS_OS_NAME ?= linux
|
||||
GO_PKG = github.com/docker/docker-credential-helpers
|
||||
GO_LDFLAGS = -s -w -X ${GO_PKG}/credentials.Version=${VERSION} -X ${GO_PKG}/credentials.Revision=${REVISION} -X ${GO_PKG}/credentials.Package=${PACKAGE}
|
||||
|
||||
all: test
|
||||
BUILDX_CMD ?= docker buildx
|
||||
DESTDIR ?= ./bin/build
|
||||
|
||||
deps:
|
||||
go get github.com/golang/lint/golint
|
||||
.PHONY: all
|
||||
all: cross
|
||||
|
||||
osxkeychain:
|
||||
mkdir -p bin
|
||||
go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf bin
|
||||
|
||||
secretservice:
|
||||
mkdir -p bin
|
||||
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
|
||||
.PHONY: build-%
|
||||
build-%: # build, can be one of build-osxkeychain build-pass build-secretservice build-wincred
|
||||
go build -trimpath -ldflags="$(GO_LDFLAGS) -X ${GO_PKG}/credentials.Name=docker-credential-$*" -o "$(DESTDIR)/docker-credential-$*" ./$*/cmd/
|
||||
|
||||
wincred:
|
||||
mkdir -p bin
|
||||
go build -o bin/docker-credential-wincred wincred/cmd/main_windows.go
|
||||
# aliases for build-* targets
|
||||
.PHONY: osxkeychain secretservice pass wincred
|
||||
osxkeychain: build-osxkeychain
|
||||
secretservice: build-secretservice
|
||||
pass: build-pass
|
||||
wincred: build-wincred
|
||||
|
||||
test:
|
||||
# tests all packages except vendor
|
||||
.PHONY: cross
|
||||
cross: # cross build all supported credential helpers
|
||||
$(BUILDX_CMD) bake binaries
|
||||
|
||||
.PHONY: release
|
||||
release: # create release
|
||||
./hack/release
|
||||
|
||||
.PHONY: test
|
||||
test: # tests all packages except vendor
|
||||
go test -v `go list ./... | grep -v /vendor/`
|
||||
|
||||
vet: vet_$(TRAVIS_OS_NAME)
|
||||
go vet ./credentials
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(BUILDX_CMD) bake lint
|
||||
|
||||
vet_osx:
|
||||
go vet ./osxkeychain
|
||||
.PHONY: validate-vendor
|
||||
validate-vendor:
|
||||
$(BUILDX_CMD) bake vendor-validate
|
||||
|
||||
vet_linux:
|
||||
go vet ./secretservice
|
||||
|
||||
validate: vet
|
||||
for p in `go list ./... | grep -v /vendor/`; do \
|
||||
golint $$p ; \
|
||||
done
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
gofmt -s -l `ls **/*.go | grep -v vendor`
|
||||
|
||||
.PHONY: validate
|
||||
validate: lint validate-vendor fmt
|
||||
|
||||
BUILDIMG:=docker-credential-secretservice-$(VERSION)
|
||||
.PHONY: deb
|
||||
deb:
|
||||
mkdir -p release
|
||||
docker build -f deb/Dockerfile \
|
||||
--build-arg VERSION=$(patsubst v%,%,$(VERSION)) \
|
||||
--build-arg REVISION=$(REVISION) \
|
||||
--tag $(BUILDIMG) \
|
||||
.
|
||||
docker run --rm --net=none $(BUILDIMG) tar cf - /release | tar xf -
|
||||
docker rmi $(BUILDIMG)
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t docker-output.XXXXXXXXXX))
|
||||
$(BUILDX_CMD) bake --set "*.output=type=local,dest=$($@_TMP_OUT)" vendor
|
||||
rm -rf ./vendor
|
||||
cp -R "$($@_TMP_OUT)"/* .
|
||||
rm -rf "$($@_TMP_OUT)"
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
[](https://github.com/docker/docker-credential-helpers/releases/latest)
|
||||
[](https://pkg.go.dev/github.com/docker/docker-credential-helpers)
|
||||
[](https://github.com/docker/docker-credential-helpers/actions?query=workflow%3Abuild)
|
||||
[](https://codecov.io/gh/docker/docker-credential-helpers)
|
||||
[](https://goreportcard.com/report/github.com/docker/docker-credential-helpers)
|
||||
|
||||
## Introduction
|
||||
|
||||
docker-credential-helpers is a suite of programs to use native stores to keep Docker credentials safe.
|
||||
@@ -6,30 +12,49 @@ docker-credential-helpers is a suite of programs to use native stores to keep Do
|
||||
|
||||
Go to the [Releases](https://github.com/docker/docker-credential-helpers/releases) page and download the binary that works better for you. Put that binary in your `$PATH`, so Docker can find it.
|
||||
|
||||
### Building from scratch
|
||||
## Building
|
||||
|
||||
The programs in this repository are written with the Go programming language. These instructions assume that you have previous knowledge about the language and you have it installed in your machine.
|
||||
You can build the credential helpers using Docker:
|
||||
|
||||
1 - Download the source and put it in your `$GOPATH` with `go get`.
|
||||
```shell
|
||||
# create builder
|
||||
$ docker buildx create --use
|
||||
|
||||
```
|
||||
$ go get github.com/docker/docker-credential-helpers
|
||||
# build credential helpers from remote repository and output to ./bin/build
|
||||
$ docker buildx bake "https://github.com/docker/docker-credential-helpers.git"
|
||||
|
||||
# or from local source
|
||||
$ git clone https://github.com/docker/docker-credential-helpers.git
|
||||
$ cd docker-credential-helpers
|
||||
$ docker buildx bake
|
||||
```
|
||||
|
||||
2 - Use `make` to build the program you want. That will leave any executable in the `bin` directory inside the repository.
|
||||
Or if the toolchain is already installed on your machine:
|
||||
|
||||
1 - Download the source.
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/docker/docker-credential-helpers.git
|
||||
$ cd docker-credential-helpers
|
||||
```
|
||||
$ cd $GOPATH/docker/docker-credentials-helpers
|
||||
|
||||
2 - Use `make` to build the program you want. That will leave an executable in the `bin` directory inside the repository.
|
||||
|
||||
```shell
|
||||
$ make osxkeychain
|
||||
```
|
||||
|
||||
3 - Put that binary in your `$PATH`, so Docker can find it.
|
||||
|
||||
```shell
|
||||
$ cp bin/build/docker-credential-osxkeychain /usr/local/bin/
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### With the Docker Engine
|
||||
|
||||
Set the `credsStore` option in your `.docker/config.json` file with the suffix of the program you want to use. For instance, set it to `osxkeychain` if you want to use `docker-credential-osxkeychain`.
|
||||
Set the `credsStore` option in your `~/.docker/config.json` file with the suffix of the program you want to use. For instance, set it to `osxkeychain` if you want to use `docker-credential-osxkeychain`.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -55,6 +80,12 @@ You can see examples of each function in the [client](https://godoc.org/github.c
|
||||
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store.
|
||||
2. secretservice: Provides a helper to use the D-Bus secret service as credentials store.
|
||||
3. wincred: Provides a helper to use Windows credentials manager as store.
|
||||
4. pass: Provides a helper to use `pass` as credentials store.
|
||||
|
||||
#### Note
|
||||
|
||||
`pass` needs to be configured for `docker-credential-pass` to work properly.
|
||||
It must be initialized with a `gpg2` key ID. Make sure your GPG key exists is in `gpg2` keyring as `pass` uses `gpg2` instead of the regular `gpg`.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -67,8 +98,8 @@ A credential helper can be any program that can read values from the standard in
|
||||
|
||||
This repository also includes libraries to implement new credentials programs in Go. Adding a new helper program is pretty easy. You can see how the OS X keychain helper works in the [osxkeychain](osxkeychain) directory.
|
||||
|
||||
1. Implement the interface `credentials.Helper` in `YOUR_PACKAGE/YOUR_PACKAGE_$GOOS.go`
|
||||
2. Create a main program in `YOUR_PACKAGE/cmd/main_$GOOS.go`.
|
||||
1. Implement the interface `credentials.Helper` in `YOUR_PACKAGE/`
|
||||
2. Create a main program in `YOUR_PACKAGE/cmd/`.
|
||||
3. Add make tasks to build your program and run tests.
|
||||
|
||||
## License
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
version: "{build}"
|
||||
|
||||
# Source Config
|
||||
clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers
|
||||
|
||||
# Build host
|
||||
|
||||
environment:
|
||||
global:
|
||||
GOPATH: c:\gopath
|
||||
CGO_ENABLED: 1
|
||||
GOVERSION: 1.6
|
||||
matrix:
|
||||
- platform: x86
|
||||
GOARCH: 386
|
||||
MSYS2_BITS: 32
|
||||
- platform: x64
|
||||
GOARCH: amd64
|
||||
MSYS2_BITS: 64
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
# Build
|
||||
|
||||
install:
|
||||
# Install Go 1.6.
|
||||
- rmdir c:\go /s /q
|
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-%GOARCH%.msi
|
||||
- msiexec /i go%GOVERSION%.windows-%GOARCH%.msi /q
|
||||
- set Path=c:\msys64\mingw%MSYS2_BITS%\bin;c:\go\bin;%Path%
|
||||
- go version
|
||||
- go env
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- go vet ./wincred
|
||||
- go test -v github.com/docker/docker-credential-helpers/wincred
|
||||
|
||||
# Equivalent to `before_deploy` phase
|
||||
after_test:
|
||||
# build binary
|
||||
- mkdir bin
|
||||
- go build -o bin/docker-credential-wincred wincred/cmd/main_windows.go
|
||||
# build zipfile, will look like docker-credential-wincred-v0.1.0-amd64.zip in the root directory
|
||||
- cd bin && 7z a ../docker-credential-wincred-%APPVEYOR_REPO_TAG_NAME%-%GOARCH%.zip docker-credential-wincred
|
||||
|
||||
# IMPORTANT All the artifacts need to be listed here, or they won't be uploaded to GitHub
|
||||
artifacts:
|
||||
- path: docker-credential-wincred-$(APPVEYOR_REPO_TAG_NAME)-$(GOARCH).zip
|
||||
name: docker-credential-wincred-$(APPVEYOR_REPO_TAG_NAME)-$(GOARCH).zip
|
||||
|
||||
deploy:
|
||||
# All the zipped artifacts will be deployed
|
||||
description: "Visit the [Changelog](https://github.com/docker/docker-credential-helpers/blob/master/CHANGELOG.md) for a detailed description of what's new in this release."
|
||||
artifact: /.*\.zip/
|
||||
auth_token:
|
||||
secure: ixWmTXZs8aV5+9s6vPXziIcdMMLd+lBVINJ0K/Sy++2wllpRxUec4/TPVKUGLqvL
|
||||
provider: GitHub
|
||||
# deploy when a new tag is pushed
|
||||
on:
|
||||
appveyor_repo_tag: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -1,15 +0,0 @@
|
||||
set -ex
|
||||
|
||||
mkdir bin
|
||||
case "$TRAVIS_OS_NAME" in
|
||||
"osx")
|
||||
go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
|
||||
cd bin
|
||||
tar czf ../docker-credential-osxkeychain-${TRAVIS_TAG}-amd64.tar.gz docker-credential-osxkeychain
|
||||
;;
|
||||
"linux")
|
||||
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
|
||||
cd bin
|
||||
tar czf ../docker-credential-secretservice-${TRAVIS_TAG}-amd64.tar.gz docker-credential-secretservice
|
||||
;;
|
||||
esac
|
||||
@@ -1,4 +0,0 @@
|
||||
set -ex
|
||||
|
||||
sh -e /etc/init.d/xvfb start
|
||||
sleep 3 # give xvfb some time to start
|
||||
+36
-2
@@ -9,12 +9,27 @@ import (
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// isValidCredsMessage checks if 'msg' contains invalid credentials error message.
|
||||
// It returns whether the logs are free of invalid credentials errors and the error if it isn't.
|
||||
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername.
|
||||
func isValidCredsMessage(msg string) error {
|
||||
if credentials.IsCredentialsMissingServerURLMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
if credentials.IsCredentialsMissingUsernameMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingUsername()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Store uses an external program to save credentials.
|
||||
func Store(program ProgramFunc, credentials *credentials.Credentials) error {
|
||||
func Store(program ProgramFunc, creds *credentials.Credentials) error {
|
||||
cmd := program("store")
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(credentials); err != nil {
|
||||
if err := json.NewEncoder(buffer).Encode(creds); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Input(buffer)
|
||||
@@ -22,6 +37,11 @@ func Store(program ProgramFunc, credentials *credentials.Credentials) error {
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
@@ -41,6 +61,10 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error
|
||||
return nil, credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
@@ -62,6 +86,11 @@ func Erase(program ProgramFunc, serverURL string) error {
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
@@ -75,6 +104,11 @@ func List(program ProgramFunc) (map[string]string, error) {
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
|
||||
+12
-7
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -32,7 +31,7 @@ type mockProgram struct {
|
||||
// Output returns responses from the remote credentials helper.
|
||||
// It mocks those responses based in the input in the mock.
|
||||
func (m *mockProgram) Output() ([]byte, error) {
|
||||
in, err := ioutil.ReadAll(m.input)
|
||||
in, err := io.ReadAll(m.input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,6 +55,8 @@ func (m *mockProgram) Output() ([]byte, error) {
|
||||
return []byte(credentials.NewErrCredentialsNotFound().Error()), errProgramExited
|
||||
case invalidServerAddress:
|
||||
return []byte("program failed"), errProgramExited
|
||||
case "":
|
||||
return []byte(credentials.NewErrCredentialsMissingServerURL().Error()), errProgramExited
|
||||
}
|
||||
case "store":
|
||||
var c credentials.Credentials
|
||||
@@ -106,8 +107,8 @@ func ExampleStore() {
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
valid := []credentials.Credentials{
|
||||
{validServerAddress, "foo", "bar"},
|
||||
{validServerAddress2, "<token>", "abcd1234"},
|
||||
{ServerURL: validServerAddress, Username: "foo", Secret: "bar"},
|
||||
{ServerURL: validServerAddress2, Username: "<token>", Secret: "abcd1234"},
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
@@ -117,7 +118,7 @@ func TestStore(t *testing.T) {
|
||||
}
|
||||
|
||||
invalid := []credentials.Credentials{
|
||||
{invalidServerAddress, "foo", "bar"},
|
||||
{ServerURL: invalidServerAddress, Username: "foo", Secret: "bar"},
|
||||
}
|
||||
|
||||
for _, v := range invalid {
|
||||
@@ -140,8 +141,8 @@ func ExampleGet() {
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
valid := []credentials.Credentials{
|
||||
{validServerAddress, "foo", "bar"},
|
||||
{validServerAddress2, "<token>", "abcd1234"},
|
||||
{ServerURL: validServerAddress, Username: "foo", Secret: "bar"},
|
||||
{ServerURL: validServerAddress2, Username: "<token>", Secret: "abcd1234"},
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
@@ -158,12 +159,16 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
missingServerURLErr := credentials.NewErrCredentialsMissingServerURL()
|
||||
|
||||
invalid := []struct {
|
||||
serverURL string
|
||||
err string
|
||||
}{
|
||||
{missingCredsAddress, credentials.NewErrCredentialsNotFound().Error()},
|
||||
{invalidServerAddress, "error getting credentials - err: exited 1, out: `program failed`"},
|
||||
{"", fmt.Sprintf("error getting credentials - err: %s, out: `%s`",
|
||||
missingServerURLErr.Error(), missingServerURLErr.Error())},
|
||||
}
|
||||
|
||||
for _, v := range invalid {
|
||||
|
||||
+22
-2
@@ -1,8 +1,11 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"os"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
// Program is an interface to execute external programs.
|
||||
@@ -16,11 +19,28 @@ type ProgramFunc func(args ...string) Program
|
||||
|
||||
// NewShellProgramFunc creates programs that are executed in a Shell.
|
||||
func NewShellProgramFunc(name string) ProgramFunc {
|
||||
return NewShellProgramFuncWithEnv(name, nil)
|
||||
}
|
||||
|
||||
// NewShellProgramFuncWithEnv creates programs that are executed in a Shell with environment variables
|
||||
func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc {
|
||||
return func(args ...string) Program {
|
||||
return &Shell{cmd: exec.Command(name, args...)}
|
||||
return &Shell{cmd: createProgramCmdRedirectErr(name, args, env)}
|
||||
}
|
||||
}
|
||||
|
||||
func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd {
|
||||
programCmd := exec.Command(commandName, args...)
|
||||
programCmd.Env = os.Environ()
|
||||
if env != nil {
|
||||
for k, v := range *env {
|
||||
programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
programCmd.Stderr = os.Stderr
|
||||
return programCmd
|
||||
}
|
||||
|
||||
// Shell invokes shell commands to talk with a remote credentials helper.
|
||||
type Shell struct {
|
||||
cmd *exec.Cmd
|
||||
|
||||
@@ -17,6 +17,32 @@ type Credentials struct {
|
||||
Secret string
|
||||
}
|
||||
|
||||
// isValid checks the integrity of Credentials object such that no credentials lack
|
||||
// a server URL or a username.
|
||||
// It returns whether the credentials are valid and the error if it isn't.
|
||||
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername
|
||||
func (c *Credentials) isValid() (bool, error) {
|
||||
if len(c.ServerURL) == 0 {
|
||||
return false, NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
if len(c.Username) == 0 {
|
||||
return false, NewErrCredentialsMissingUsername()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CredsLabel holds the way Docker credentials should be labeled as such in credentials stores that allow labelling.
|
||||
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
|
||||
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
|
||||
var CredsLabel = "Docker Credentials"
|
||||
|
||||
// SetCredsLabel is a simple setter for CredsLabel
|
||||
func SetCredsLabel(label string) {
|
||||
CredsLabel = label
|
||||
}
|
||||
|
||||
// Serve initializes the credentials helper and parses the action argument.
|
||||
// This function is designed to be called from a command line interface.
|
||||
// It uses os.Args[1] as the key for the action.
|
||||
@@ -25,7 +51,7 @@ type Credentials struct {
|
||||
func Serve(helper Helper) {
|
||||
var err error
|
||||
if len(os.Args) != 2 {
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase|list>", os.Args[0])
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase|list|version>", os.Args[0])
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -49,6 +75,8 @@ func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error
|
||||
return Erase(helper, in)
|
||||
case "list":
|
||||
return List(helper, out)
|
||||
case "version":
|
||||
return PrintVersion(out)
|
||||
}
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
@@ -72,6 +100,10 @@ func Store(helper Helper, reader io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok, err := creds.isValid(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return helper.Add(&creds)
|
||||
}
|
||||
|
||||
@@ -91,6 +123,9 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
if len(serverURL) == 0 {
|
||||
return NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
username, secret, err := helper.Get(serverURL)
|
||||
if err != nil {
|
||||
@@ -98,8 +133,9 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
}
|
||||
|
||||
resp := Credentials{
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
ServerURL: serverURL,
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
@@ -126,12 +162,15 @@ func Erase(helper Helper, reader io.Reader) error {
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
if len(serverURL) == 0 {
|
||||
return NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
return helper.Delete(serverURL)
|
||||
}
|
||||
|
||||
//List returns all the serverURLs of keys in
|
||||
//the OS store as a list of strings
|
||||
// List returns all the serverURLs of keys in
|
||||
// the OS store as a list of strings
|
||||
func List(helper Helper, writer io.Writer) error {
|
||||
accts, err := helper.List()
|
||||
if err != nil {
|
||||
@@ -139,3 +178,9 @@ func List(helper Helper, writer io.Writer) error {
|
||||
}
|
||||
return json.NewEncoder(writer).Encode(accts)
|
||||
}
|
||||
|
||||
// PrintVersion outputs the current version.
|
||||
func PrintVersion(writer io.Writer) error {
|
||||
fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -73,6 +73,46 @@ func TestStore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreMissingServerURL(t *testing.T) {
|
||||
creds := &Credentials{
|
||||
ServerURL: "",
|
||||
Username: "foo",
|
||||
Secret: "bar",
|
||||
}
|
||||
|
||||
b, err := json.Marshal(creds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
|
||||
if err := Store(h, in); IsCredentialsMissingServerURL(err) == false {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreMissingUsername(t *testing.T) {
|
||||
creds := &Credentials{
|
||||
ServerURL: "https://index.docker.io/v1/",
|
||||
Username: "",
|
||||
Secret: "bar",
|
||||
}
|
||||
|
||||
b, err := json.Marshal(creds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
|
||||
if err := Store(h, in); IsCredentialsMissingUsername(err) == false {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
creds := &Credentials{
|
||||
@@ -115,6 +155,32 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMissingServerURL(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
Secret: "bar",
|
||||
}
|
||||
b, err := json.Marshal(creds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
if err := Store(h, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := strings.NewReader("")
|
||||
w := new(bytes.Buffer)
|
||||
|
||||
if err := Get(h, buf, w); IsCredentialsMissingServerURL(err) == false {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErase(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
creds := &Credentials{
|
||||
@@ -144,6 +210,30 @@ func TestErase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEraseMissingServerURL(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
Secret: "bar",
|
||||
}
|
||||
b, err := json.Marshal(creds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
if err := Store(h, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := strings.NewReader("")
|
||||
if err := Erase(h, buf); IsCredentialsMissingServerURL(err) == false {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
//This tests that there is proper input an output into the byte stream
|
||||
//Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively
|
||||
|
||||
+68
-3
@@ -1,8 +1,15 @@
|
||||
package credentials
|
||||
|
||||
// ErrCredentialsNotFound standarizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
const errCredentialsNotFoundMessage = "credentials not found in native keychain"
|
||||
const (
|
||||
// ErrCredentialsNotFound standardizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
errCredentialsNotFoundMessage = "credentials not found in native keychain"
|
||||
|
||||
// ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize
|
||||
// invalid credentials or credentials management operations
|
||||
errCredentialsMissingServerURLMessage = "no credentials server URL"
|
||||
errCredentialsMissingUsernameMessage = "no credentials username"
|
||||
)
|
||||
|
||||
// errCredentialsNotFound represents an error
|
||||
// raised when credentials are not in the store.
|
||||
@@ -35,3 +42,61 @@ func IsErrCredentialsNotFound(err error) bool {
|
||||
func IsErrCredentialsNotFoundMessage(err string) bool {
|
||||
return err == errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// errCredentialsMissingServerURL represents an error raised
|
||||
// when the credentials object has no server URL or when no
|
||||
// server URL is provided to a credentials operation requiring
|
||||
// one.
|
||||
type errCredentialsMissingServerURL struct{}
|
||||
|
||||
func (errCredentialsMissingServerURL) Error() string {
|
||||
return errCredentialsMissingServerURLMessage
|
||||
}
|
||||
|
||||
// errCredentialsMissingUsername represents an error raised
|
||||
// when the credentials object has no username or when no
|
||||
// username is provided to a credentials operation requiring
|
||||
// one.
|
||||
type errCredentialsMissingUsername struct{}
|
||||
|
||||
func (errCredentialsMissingUsername) Error() string {
|
||||
return errCredentialsMissingUsernameMessage
|
||||
}
|
||||
|
||||
// NewErrCredentialsMissingServerURL creates a new error for
|
||||
// errCredentialsMissingServerURL.
|
||||
func NewErrCredentialsMissingServerURL() error {
|
||||
return errCredentialsMissingServerURL{}
|
||||
}
|
||||
|
||||
// NewErrCredentialsMissingUsername creates a new error for
|
||||
// errCredentialsMissingUsername.
|
||||
func NewErrCredentialsMissingUsername() error {
|
||||
return errCredentialsMissingUsername{}
|
||||
}
|
||||
|
||||
// IsCredentialsMissingServerURL returns true if the error
|
||||
// was an errCredentialsMissingServerURL.
|
||||
func IsCredentialsMissingServerURL(err error) bool {
|
||||
_, ok := err.(errCredentialsMissingServerURL)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsCredentialsMissingServerURLMessage checks for an
|
||||
// errCredentialsMissingServerURL in the error message.
|
||||
func IsCredentialsMissingServerURLMessage(err string) bool {
|
||||
return err == errCredentialsMissingServerURLMessage
|
||||
}
|
||||
|
||||
// IsCredentialsMissingUsername returns true if the error
|
||||
// was an errCredentialsMissingUsername.
|
||||
func IsCredentialsMissingUsername(err error) bool {
|
||||
_, ok := err.(errCredentialsMissingUsername)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsCredentialsMissingUsernameMessage checks for an
|
||||
// errCredentialsMissingUsername in the error message.
|
||||
func IsCredentialsMissingUsernameMessage(err string) bool {
|
||||
return err == errCredentialsMissingUsernameMessage
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package credentials
|
||||
|
||||
var (
|
||||
// Name is filled at linking time
|
||||
Name = ""
|
||||
|
||||
// Package is filled at linking time
|
||||
Package = "github.com/docker/docker-credential-helpers"
|
||||
|
||||
// Version holds the complete version number. Filled in at linking time.
|
||||
Version = "v0.0.0+unknown"
|
||||
|
||||
// Revision is filled with the VCS (e.g. git) revision being used to build
|
||||
// the program at linking time.
|
||||
Revision = ""
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
ARG GO_VERSION=1.18.5
|
||||
ARG DISTRO=ubuntu
|
||||
ARG SUITE=focal
|
||||
|
||||
FROM golang:${GO_VERSION}-buster AS golang
|
||||
|
||||
FROM ${DISTRO}:${SUITE}
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -yy debhelper dh-make libsecret-1-dev
|
||||
RUN mkdir -p /build
|
||||
|
||||
WORKDIR /build
|
||||
ENV GOPROXY=https://proxy.golang.org|direct
|
||||
ENV GO111MODULE=off
|
||||
ENV GOPATH=/build
|
||||
ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
|
||||
COPY --from=golang /usr/local/go /usr/local/go
|
||||
|
||||
COPY Makefile .
|
||||
COPY credentials credentials
|
||||
COPY secretservice secretservice
|
||||
COPY pass pass
|
||||
COPY deb/debian ./debian
|
||||
COPY deb/build-deb .
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=${VERSION}
|
||||
ARG REVISION
|
||||
ENV REVISION=${REVISION}
|
||||
ARG DISTRO
|
||||
ENV DISTRO=${DISTRO}
|
||||
ARG SUITE
|
||||
ENV SUITE=${SUITE}
|
||||
RUN /build/build-deb
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
maintainer=$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' debian/control)
|
||||
|
||||
cat > "debian/changelog" <<-EOF
|
||||
docker-credential-helpers ($VERSION) $DISTRO-$SUITE; urgency=low
|
||||
|
||||
* New upstream version
|
||||
|
||||
-- $maintainer $(date --rfc-2822)
|
||||
EOF
|
||||
|
||||
mkdir -p src/github.com/docker/docker-credential-helpers
|
||||
ln -s /build/credentials /build/src/github.com/docker/docker-credential-helpers/credentials
|
||||
ln -s /build/secretservice /build/src/github.com/docker/docker-credential-helpers/secretservice
|
||||
ln -s /build/pass /build/src/github.com/docker/docker-credential-helpers/pass
|
||||
|
||||
dpkg-buildpackage -us -uc
|
||||
|
||||
mkdir /release
|
||||
mv /docker-credential-* /release
|
||||
@@ -0,0 +1 @@
|
||||
9
|
||||
@@ -0,0 +1,25 @@
|
||||
Source: docker-credential-helpers
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Docker <support@docker.com>
|
||||
Homepage: https://dockerproject.org
|
||||
Standards-Version: 3.9.6
|
||||
Vcs-Browser: https://github.com/docker/docker-credential-helpers
|
||||
Vcs-Git: git://github.com/docker/docker-credential-helpers.git
|
||||
Build-Depends: debhelper
|
||||
, dh-make
|
||||
, libsecret-1-dev
|
||||
|
||||
Package: docker-credential-secretservice
|
||||
Architecture: any
|
||||
Depends: libsecret-1-0
|
||||
, ${misc:Depends}
|
||||
Description: docker-credential-secretservice is a credential helper backend
|
||||
which uses libsecret to keep Docker credentials safe.
|
||||
|
||||
Package: docker-credential-pass
|
||||
Architecture: any
|
||||
Depends: pass
|
||||
, ${misc:Depends}
|
||||
Description: docker-credential-secretservice is a credential helper backend
|
||||
which uses the pass utility to keep Docker credentials safe.
|
||||
@@ -0,0 +1 @@
|
||||
debian/tmp/usr/bin/docker-credential-pass
|
||||
@@ -0,0 +1 @@
|
||||
debian/tmp/usr/bin/docker-credential-secretservice
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
DESTDIR := $(CURDIR)/debian/tmp
|
||||
|
||||
override_dh_auto_build:
|
||||
make build-secretservice DESTDIR=bin
|
||||
make build-pass DESTDIR=bin
|
||||
|
||||
override_dh_auto_install:
|
||||
install -D bin/docker-credential-secretservice $(DESTDIR)/usr/bin/docker-credential-secretservice
|
||||
install -D bin/docker-credential-pass $(DESTDIR)/usr/bin/docker-credential-pass
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_test:
|
||||
# no tests
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.18.5"
|
||||
}
|
||||
|
||||
# Defines the output folder
|
||||
variable "DESTDIR" {
|
||||
default = ""
|
||||
}
|
||||
function "bindir" {
|
||||
params = [defaultdir]
|
||||
result = DESTDIR != "" ? DESTDIR : "./bin/${defaultdir}"
|
||||
}
|
||||
|
||||
target "_common" {
|
||||
args = {
|
||||
GO_VERSION = GO_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["binaries"]
|
||||
}
|
||||
|
||||
group "validate" {
|
||||
targets = ["lint", "vendor-validate"]
|
||||
}
|
||||
|
||||
target "lint" {
|
||||
inherits = ["_common"]
|
||||
target = "lint"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "test" {
|
||||
inherits = ["_common"]
|
||||
target = "test-coverage"
|
||||
output = [bindir("coverage")]
|
||||
}
|
||||
|
||||
target "binaries" {
|
||||
inherits = ["_common"]
|
||||
target = "binaries"
|
||||
output = [bindir("build")]
|
||||
platforms = [
|
||||
"darwin/amd64",
|
||||
"darwin/arm64",
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"linux/arm/v7",
|
||||
"linux/arm/v6",
|
||||
"linux/ppc64le",
|
||||
"linux/s390x",
|
||||
"windows/amd64"
|
||||
]
|
||||
}
|
||||
|
||||
target "release" {
|
||||
inherits = ["binaries"]
|
||||
target = "release"
|
||||
output = [bindir("release")]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module github.com/docker/docker-credential-helpers
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/danieljoos/wincred v1.1.2
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
"version")
|
||||
git describe --match 'v[0-9]*' --dirty='.m' --always --tags
|
||||
;;
|
||||
"revision")
|
||||
echo "$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)"
|
||||
;;
|
||||
*)
|
||||
echo "usage: ./hack/git-meta <version|revision>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
: "${BUILDX_CMD=docker buildx}"
|
||||
: "${DESTDIR=./bin/release}"
|
||||
: "${CACHE_FROM=}"
|
||||
: "${CACHE_TO=}"
|
||||
|
||||
: "${SIGN=}"
|
||||
: "${PFX=}"
|
||||
: "${PFXPASSWORD=}"
|
||||
|
||||
if [ -n "$CACHE_FROM" ]; then
|
||||
for cfrom in $CACHE_FROM; do
|
||||
cacheFlags+=(--set "*.cache-from=$cfrom")
|
||||
done
|
||||
fi
|
||||
if [ -n "$CACHE_TO" ]; then
|
||||
for cto in $CACHE_TO; do
|
||||
cacheFlags+=(--set "*.cache-to=$cto")
|
||||
done
|
||||
fi
|
||||
|
||||
dockerpfx=$(mktemp -t dockercredhelper-pfx.XXXXXXXXXX)
|
||||
function clean {
|
||||
rm -f "$dockerpfx"
|
||||
}
|
||||
trap clean EXIT
|
||||
|
||||
# release
|
||||
(
|
||||
set -x
|
||||
${BUILDX_CMD} bake "${cacheFlags[@]}" --set "*.output=$DESTDIR" release
|
||||
)
|
||||
|
||||
# wrap binaries
|
||||
mv -f ./${DESTDIR}/**/* ./${DESTDIR}/
|
||||
find ./${DESTDIR} -type d -empty -delete
|
||||
|
||||
# sign binaries
|
||||
if [ -n "$SIGN" ]; then
|
||||
for f in "${DESTDIR}"/*".darwin-"*; do
|
||||
SIGNINGHASH=$(security find-identity -v -p codesigning | grep "Developer ID Application: Docker Inc" | cut -d ' ' -f 4)
|
||||
xcrun -log codesign -s "$SIGNINGHASH" --force --verbose "$f"
|
||||
xcrun codesign --verify --deep --strict --verbose=2 --display "$f"
|
||||
done
|
||||
for f in "${DESTDIR}"/*".windows-"*; do
|
||||
echo ${PFX} | base64 -d > "$dockerpfx"
|
||||
signtool sign /fd SHA256 /a /f pfx /p ${PFXPASSWORD} /d Docker /du https://www.docker.com /t http://timestamp.verisign.com/scripts/timestamp.dll "$f"
|
||||
done
|
||||
fi
|
||||
|
||||
# checksums
|
||||
(
|
||||
cd ${DESTDIR}
|
||||
sha256sum -b docker-credential-* > ./checksums.txt
|
||||
sha256sum -c --strict checksums.txt
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Foundation/NSValue.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -13,7 +14,9 @@ char *get_error(OSStatus status) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *keychain_add(struct Server *server, char *username, char *secret) {
|
||||
char *keychain_add(struct Server *server, char *label, char *username, char *secret) {
|
||||
SecKeychainItemRef item;
|
||||
|
||||
OSStatus status = SecKeychainAddInternetPassword(
|
||||
NULL,
|
||||
strlen(server->host), server->host,
|
||||
@@ -24,11 +27,27 @@ char *keychain_add(struct Server *server, char *username, char *secret) {
|
||||
server->proto,
|
||||
kSecAuthenticationTypeDefault,
|
||||
strlen(secret), secret,
|
||||
NULL
|
||||
&item
|
||||
);
|
||||
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
SecKeychainAttribute attribute;
|
||||
SecKeychainAttributeList attrs;
|
||||
attribute.tag = kSecLabelItemAttr;
|
||||
attribute.data = label;
|
||||
attribute.length = strlen(label);
|
||||
attrs.count = 1;
|
||||
attrs.attr = &attribute;
|
||||
|
||||
status = SecKeychainItemModifyContent(item, &attrs, 0, NULL);
|
||||
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -115,44 +134,42 @@ char * CFStringToCharArr(CFStringRef aString) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
|
||||
char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) {
|
||||
CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8);
|
||||
CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
|
||||
CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
|
||||
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
|
||||
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
|
||||
CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF);
|
||||
//Use this query dictionary
|
||||
CFTypeRef result= NULL;
|
||||
OSStatus status = SecItemCopyMatching(
|
||||
query,
|
||||
&result);
|
||||
query,
|
||||
&result);
|
||||
|
||||
CFRelease(credsLabelCF);
|
||||
|
||||
//Ran a search and store the results in result
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
int numKeys = CFArrayGetCount(result);
|
||||
CFIndex numKeys = CFArrayGetCount(result);
|
||||
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
//result is of type CFArray
|
||||
for(int i=0; i<numKeys; i++) {
|
||||
for(CFIndex i=0; i<numKeys; i++) {
|
||||
CFDictionaryRef currKey = CFArrayGetValueAtIndex(result,i);
|
||||
if (CFDictionaryContainsKey(currKey, CFSTR("path"))) {
|
||||
//Even if a key is stored without an account, Apple defaults it to null so these arrays will be of the same length
|
||||
CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
|
||||
CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
|
||||
if (acctTmp == NULL) {
|
||||
acctTmp = CFSTR("account not defined");
|
||||
|
||||
CFStringRef protocolTmp = CFDictionaryGetValue(currKey, CFSTR("ptcl"));
|
||||
if (protocolTmp != NULL) {
|
||||
CFStringRef protocolStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), protocolTmp);
|
||||
if (CFStringCompare(protocolStr, CFSTR("htps"), 0) == kCFCompareEqualTo) {
|
||||
protocolTmp = CFSTR("https://");
|
||||
}
|
||||
char * path = (char *) malloc(CFStringGetLength(pathTmp)+1);
|
||||
path = CFStringToCharArr(pathTmp);
|
||||
path[strlen(path)] = '\0';
|
||||
char * acct = (char *) malloc(CFStringGetLength(acctTmp)+1);
|
||||
acct = CFStringToCharArr(acctTmp);
|
||||
acct[strlen(acct)] = '\0';
|
||||
//We now have all we need, username and servername. Now export this to .go
|
||||
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
|
||||
else {
|
||||
protocolTmp = CFSTR("http://");
|
||||
}
|
||||
CFRelease(protocolStr);
|
||||
}
|
||||
else {
|
||||
char * path = "0";
|
||||
@@ -161,9 +178,45 @@ char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)));
|
||||
continue;
|
||||
}
|
||||
|
||||
CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, protocolTmp);
|
||||
CFStringRef serverTmp = CFDictionaryGetValue(currKey, CFSTR("srvr"));
|
||||
if (serverTmp != NULL) {
|
||||
CFStringAppend(str, serverTmp);
|
||||
}
|
||||
|
||||
CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
|
||||
if (pathTmp != NULL) {
|
||||
CFStringAppend(str, pathTmp);
|
||||
}
|
||||
|
||||
const NSNumber * portTmp = CFDictionaryGetValue(currKey, CFSTR("port"));
|
||||
if (portTmp != NULL && portTmp.integerValue != 0) {
|
||||
CFStringRef portStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), portTmp);
|
||||
CFStringAppend(str, CFSTR(":"));
|
||||
CFStringAppend(str, portStr);
|
||||
CFRelease(portStr);
|
||||
}
|
||||
|
||||
CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
|
||||
if (acctTmp == NULL) {
|
||||
acctTmp = CFSTR("account not defined");
|
||||
}
|
||||
|
||||
char * path = CFStringToCharArr(str);
|
||||
char * acct = CFStringToCharArr(acctTmp);
|
||||
|
||||
//We now have all we need, username and servername. Now export this to .go
|
||||
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
|
||||
|
||||
CFRelease(str);
|
||||
}
|
||||
*list_l = numKeys;
|
||||
*list_l = (int)numKeys;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -171,5 +224,4 @@ void freeListData(char *** data, unsigned int length) {
|
||||
for(int i=0; i<length; i++) {
|
||||
free((*data)[i]);
|
||||
}
|
||||
free(*data);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package osxkeychain
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.10
|
||||
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.11
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.11
|
||||
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include <stdlib.h>
|
||||
@@ -10,18 +10,24 @@ package osxkeychain
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker-credential-helpers/registryurl"
|
||||
)
|
||||
|
||||
// errCredentialsNotFound is the specific error message returned by OS X
|
||||
// when the credentials are not in the keychain.
|
||||
const errCredentialsNotFound = "The specified item could not be found in the keychain."
|
||||
|
||||
// errCredentialsNotFound is the specific error message returned by OS X
|
||||
// when environment does not allow showing dialog to unlock keychain.
|
||||
const errInteractionNotAllowed = "User interaction is not allowed."
|
||||
|
||||
// ErrInteractionNotAllowed is returned if keychain password prompt can not be shown.
|
||||
var ErrInteractionNotAllowed = errors.New(`keychain cannot be accessed because the current session does not allow user interaction. The keychain may be locked; unlock it by running "security -v unlock-keychain ~/Library/Keychains/login.keychain-db" and try again`)
|
||||
|
||||
// Osxkeychain handles secrets using the OS X Keychain as store.
|
||||
type Osxkeychain struct{}
|
||||
|
||||
@@ -35,12 +41,14 @@ func (h Osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
}
|
||||
defer freeServer(s)
|
||||
|
||||
label := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(label))
|
||||
username := C.CString(creds.Username)
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
secret := C.CString(creds.Secret)
|
||||
defer C.free(unsafe.Pointer(secret))
|
||||
|
||||
errMsg := C.keychain_add(s, username, secret)
|
||||
errMsg := C.keychain_add(s, label, username, secret)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
@@ -88,6 +96,9 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
if goMsg == errCredentialsNotFound {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
if goMsg == errInteractionNotAllowed {
|
||||
return "", "", ErrInteractionNotAllowed
|
||||
}
|
||||
|
||||
return "", "", errors.New(goMsg)
|
||||
}
|
||||
@@ -99,26 +110,35 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
|
||||
// List returns the stored URLs and corresponding usernames.
|
||||
func (h Osxkeychain) List() (map[string]string, error) {
|
||||
credsLabelC := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabelC))
|
||||
|
||||
var pathsC **C.char
|
||||
defer C.free(unsafe.Pointer(pathsC))
|
||||
var acctsC **C.char
|
||||
defer C.free(unsafe.Pointer(acctsC))
|
||||
var listLenC C.uint
|
||||
errMsg := C.keychain_list(&pathsC, &acctsC, &listLenC)
|
||||
errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
|
||||
defer C.freeListData(&pathsC, listLenC)
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
if goMsg == errCredentialsNotFound {
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
if goMsg == errInteractionNotAllowed {
|
||||
return nil, ErrInteractionNotAllowed
|
||||
}
|
||||
|
||||
return nil, errors.New(goMsg)
|
||||
}
|
||||
|
||||
defer C.freeListData(&pathsC, listLenC)
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
|
||||
var listLen int
|
||||
listLen = int(listLenC)
|
||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
//taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
|
||||
// taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
|
||||
resp := make(map[string]string)
|
||||
for i := 0; i < listLen; i++ {
|
||||
if C.GoString(pathTmp[i]) == "0" {
|
||||
@@ -130,30 +150,27 @@ func (h Osxkeychain) List() (map[string]string, error) {
|
||||
}
|
||||
|
||||
func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||
u, err := url.Parse(serverURL)
|
||||
u, err := registryurl.Parse(serverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostAndPort := strings.Split(u.Host, ":")
|
||||
host := hostAndPort[0]
|
||||
proto := C.kSecProtocolTypeHTTPS
|
||||
if u.Scheme == "http" {
|
||||
proto = C.kSecProtocolTypeHTTP
|
||||
}
|
||||
var port int
|
||||
if len(hostAndPort) == 2 {
|
||||
p, err := strconv.Atoi(hostAndPort[1])
|
||||
p := u.Port()
|
||||
if p != "" {
|
||||
port, err = strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port = p
|
||||
}
|
||||
|
||||
proto := C.kSecProtocolTypeHTTPS
|
||||
if u.Scheme != "https" {
|
||||
proto = C.kSecProtocolTypeHTTP
|
||||
}
|
||||
|
||||
return &C.struct_Server{
|
||||
proto: C.SecProtocolType(proto),
|
||||
host: C.CString(host),
|
||||
host: C.CString(u.Hostname()),
|
||||
port: C.uint(port),
|
||||
path: C.CString(u.Path),
|
||||
}, nil
|
||||
|
||||
@@ -7,8 +7,8 @@ struct Server {
|
||||
unsigned int port;
|
||||
};
|
||||
|
||||
char *keychain_add(struct Server *server, char *username, char *secret);
|
||||
char *keychain_add(struct Server *server, char *label, char *username, char *secret);
|
||||
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
|
||||
char *keychain_delete(struct Server *server);
|
||||
char *keychain_list(char *** data, char *** accts, unsigned int *list_l);
|
||||
char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l);
|
||||
void freeListData(char *** data, unsigned int length);
|
||||
@@ -1,8 +1,10 @@
|
||||
package osxkeychain
|
||||
|
||||
import (
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
func TestOSXKeychainHelper(t *testing.T) {
|
||||
@@ -54,6 +56,154 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
|
||||
// through variations on the URL
|
||||
func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored with port, retrieved without
|
||||
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
|
||||
|
||||
// stored as https, retrieved without scheme
|
||||
{"https://foobar.docker.io:2376", "foobar.docker.io"},
|
||||
|
||||
// stored with path, retrieved without
|
||||
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
|
||||
// returned.
|
||||
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored as https, retrieved using http
|
||||
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
|
||||
|
||||
// stored as http, retrieved using https
|
||||
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
|
||||
|
||||
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
|
||||
{"http://foobar.docker.io", "foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports
|
||||
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports TODO is this desired behavior? The other way round does work
|
||||
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching paths
|
||||
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err == nil {
|
||||
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the
|
||||
// the keychain can be read back using the URL that was used to store them.
|
||||
func TestOSXKeychainHelperStoreRetrieve(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
}{
|
||||
{url: "foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
|
||||
// Note that we don't delete between individual tests here, to verify that
|
||||
// subsequent stores/overwrites don't affect storing / retrieving secrets.
|
||||
for i, te := range tests {
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: te.url,
|
||||
Username: fmt.Sprintf("user-%d", i),
|
||||
Secret: fmt.Sprintf("secret-%d", i),
|
||||
}
|
||||
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
user, secret, err := helper.Get(te.url)
|
||||
if err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
if user != c.Username {
|
||||
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
|
||||
}
|
||||
if secret != c.Secret {
|
||||
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := Osxkeychain{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker-credential-helpers/pass"
|
||||
)
|
||||
|
||||
func main() {
|
||||
credentials.Serve(pass.Pass{})
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
// Package pass implements a `pass` based credential helper. Passwords are stored
|
||||
// as arguments to pass of the form: "$PASS_FOLDER/base64-url(serverURL)/username".
|
||||
// We base64-url encode the serverURL, because under the hood pass uses files and
|
||||
// folders, so /s will get translated into additional folders.
|
||||
package pass
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// PASS_FOLDER contains the directory where credentials are stored
|
||||
const PASS_FOLDER = "docker-credential-helpers" //nolint:revive
|
||||
|
||||
// Pass handles secrets using pass as a store.
|
||||
type Pass struct{}
|
||||
|
||||
// Ideally these would be stored as members of Pass, but since all of Pass's
|
||||
// methods have value receivers, not pointer receivers, and changing that is
|
||||
// backwards incompatible, we assume that all Pass instances share the same configuration
|
||||
|
||||
// initializationMutex is held while initializing so that only one 'pass'
|
||||
// round-tripping is done to check pass is functioning.
|
||||
var initializationMutex sync.Mutex
|
||||
var passInitialized bool
|
||||
|
||||
// CheckInitialized checks whether the password helper can be used. It
|
||||
// internally caches and so may be safely called multiple times with no impact
|
||||
// on performance, though the first call may take longer.
|
||||
func (p Pass) CheckInitialized() bool {
|
||||
return p.checkInitialized() == nil
|
||||
}
|
||||
|
||||
func (p Pass) checkInitialized() error {
|
||||
initializationMutex.Lock()
|
||||
defer initializationMutex.Unlock()
|
||||
if passInitialized {
|
||||
return nil
|
||||
}
|
||||
// We just run a `pass ls`, if it fails then pass is not initialized.
|
||||
_, err := p.runPassHelper("", "ls")
|
||||
if err != nil {
|
||||
return fmt.Errorf("pass not initialized: %v", err)
|
||||
}
|
||||
passInitialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Pass) runPass(stdinContent string, args ...string) (string, error) {
|
||||
if err := p.checkInitialized(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.runPassHelper(stdinContent, args...)
|
||||
}
|
||||
|
||||
func (p Pass) runPassHelper(stdinContent string, args ...string) (string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.Command("pass", args...)
|
||||
cmd.Stdin = strings.NewReader(stdinContent)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
// trim newlines; pass v1.7.1+ includes a newline at the end of `show` output
|
||||
return strings.TrimRight(stdout.String(), "\n\r"), nil
|
||||
}
|
||||
|
||||
// Add adds new credentials to the keychain.
|
||||
func (p Pass) Add(creds *credentials.Credentials) error {
|
||||
if creds == nil {
|
||||
return errors.New("missing credentials")
|
||||
}
|
||||
|
||||
encoded := base64.URLEncoding.EncodeToString([]byte(creds.ServerURL))
|
||||
|
||||
_, err := p.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username))
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete removes credentials from the store.
|
||||
func (p Pass) Delete(serverURL string) error {
|
||||
if serverURL == "" {
|
||||
return errors.New("missing server url")
|
||||
}
|
||||
|
||||
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
|
||||
_, err := p.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded))
|
||||
return err
|
||||
}
|
||||
|
||||
func getPassDir() string {
|
||||
passDir := "$HOME/.password-store"
|
||||
if envDir := os.Getenv("PASSWORD_STORE_DIR"); envDir != "" {
|
||||
passDir = envDir
|
||||
}
|
||||
return os.ExpandEnv(passDir)
|
||||
}
|
||||
|
||||
// listPassDir lists all the contents of a directory in the password store.
|
||||
// Pass uses fancy unicode to emit stuff to stdout, so rather than try
|
||||
// and parse this, let's just look at the directory structure instead.
|
||||
func listPassDir(args ...string) ([]os.FileInfo, error) {
|
||||
passDir := getPassDir()
|
||||
p := path.Join(append([]string{passDir, PASS_FOLDER}, args...)...)
|
||||
entries, err := os.ReadDir(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []os.FileInfo{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
infos := make([]fs.FileInfo, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// Get returns the username and secret to use for a given registry server URL.
|
||||
func (p Pass) Get(serverURL string) (string, string, error) {
|
||||
if serverURL == "" {
|
||||
return "", "", errors.New("missing server url")
|
||||
}
|
||||
|
||||
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
|
||||
|
||||
if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
usernames, err := listPassDir(encoded)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if len(usernames) < 1 {
|
||||
return "", "", fmt.Errorf("no usernames for %s", serverURL)
|
||||
}
|
||||
|
||||
actual := strings.TrimSuffix(usernames[0].Name(), ".gpg")
|
||||
secret, err := p.runPass("", "show", path.Join(PASS_FOLDER, encoded, actual))
|
||||
return actual, secret, err
|
||||
}
|
||||
|
||||
// List returns the stored URLs and corresponding usernames for a given credentials label
|
||||
func (p Pass) List() (map[string]string, error) {
|
||||
servers, err := listPassDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := map[string]string{}
|
||||
|
||||
for _, server := range servers {
|
||||
if !server.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
serverURL, err := base64.URLEncoding.DecodeString(server.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usernames, err := listPassDir(server.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(usernames) < 1 {
|
||||
return nil, fmt.Errorf("no usernames for %s", serverURL)
|
||||
}
|
||||
|
||||
resp[string(serverURL)] = strings.TrimSuffix(usernames[0].Name(), ".gpg")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
func TestPassHelper(t *testing.T) {
|
||||
helper := Pass{}
|
||||
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v1",
|
||||
Username: "nothing",
|
||||
Secret: "isthebestmeshuggahalbum",
|
||||
}
|
||||
|
||||
helper.Add(creds)
|
||||
|
||||
creds.ServerURL = "https://foobar.docker.io:9999/v2"
|
||||
helper.Add(creds)
|
||||
|
||||
credsList, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for server, username := range credsList {
|
||||
if !(strings.Contains(server, "2376") ||
|
||||
strings.Contains(server, "9999")) {
|
||||
t.Fatalf("invalid url: %s", creds.ServerURL)
|
||||
}
|
||||
|
||||
if username != "nothing" {
|
||||
t.Fatalf("invalid username: %v", username)
|
||||
}
|
||||
|
||||
u, s, err := helper.Get(server)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if u != username {
|
||||
t.Fatalf("invalid username %s", u)
|
||||
}
|
||||
|
||||
if s != "isthebestmeshuggahalbum" {
|
||||
t.Fatalf("invalid secret: %s", s)
|
||||
}
|
||||
|
||||
err = helper.Delete(server)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, _, err = helper.Get(server)
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected credentials not found, actual: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
credsList, err = helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(credsList) != 0 {
|
||||
t.Fatal("didn't delete all creds?")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCred(t *testing.T) {
|
||||
helper := Pass{}
|
||||
|
||||
_, _, err := helper.Get("garbage")
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected credentials not found, actual: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package registryurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse parses and validates a given serverURL to an url.URL, and
|
||||
// returns an error if validation failed. Querystring parameters are
|
||||
// omitted in the resulting URL, because they are not used in the helper.
|
||||
//
|
||||
// If serverURL does not have a valid scheme, `//` is used as scheme
|
||||
// before parsing. This prevents the hostname being used as path,
|
||||
// and the credentials being stored without host.
|
||||
func Parse(registryURL string) (*url.URL, error) {
|
||||
// Check if registryURL has a scheme, otherwise add `//` as scheme.
|
||||
if !strings.Contains(registryURL, "://") && !strings.HasPrefix(registryURL, "//") {
|
||||
registryURL = "//" + registryURL
|
||||
}
|
||||
|
||||
u, err := url.Parse(registryURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Scheme != "" && u.Scheme != "https" && u.Scheme != "http" {
|
||||
return nil, errors.New("unsupported scheme: " + u.Scheme)
|
||||
}
|
||||
|
||||
if u.Hostname() == "" {
|
||||
return nil, errors.New("no hostname in URL")
|
||||
}
|
||||
|
||||
u.RawQuery = ""
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// GetHostname returns the hostname of the URL
|
||||
//
|
||||
// Deprecated: use url.Hostname()
|
||||
func GetHostname(u *url.URL) string {
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// GetPort returns the port number of the URL
|
||||
//
|
||||
// Deprecated: use url.Port()
|
||||
func GetPort(u *url.URL) string {
|
||||
return u.Port()
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package registryurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestHelperParseURL verifies that a // "scheme" is added to URLs,
|
||||
// and that invalid URLs produce an error.
|
||||
func TestHelperParseURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
expectedURL string
|
||||
err error
|
||||
}{
|
||||
{url: "foobar.docker.io", expectedURL: "//foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376", expectedURL: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376", expectedURL: "https://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path", expectedURL: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar", expectedURL: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "/foobar.docker.io", err: errors.New("no hostname in URL")},
|
||||
{url: "ftp://foobar.docker.io:2376", err: errors.New("unsupported scheme: ftp")},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
u, err := Parse(te.url)
|
||||
|
||||
if te.err == nil && err != nil {
|
||||
t.Errorf("Error: failed to parse URL %q: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
if te.err != nil && err == nil {
|
||||
t.Errorf("Error: expected error %q, got none when parsing URL %q", te.err, te.url)
|
||||
continue
|
||||
}
|
||||
if te.err != nil && err.Error() != te.err.Error() {
|
||||
t.Errorf("Error: expected error %q, got %q when parsing URL %q", te.err, err, te.url)
|
||||
continue
|
||||
}
|
||||
if u != nil && u.String() != te.expectedURL {
|
||||
t.Errorf("Error: expected URL: %q, but got %q for URL: %q", te.expectedURL, u.String(), te.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ const SecretSchema *docker_get_schema(void)
|
||||
static const SecretSchema docker_schema = {
|
||||
"io.docker.Credentials", SECRET_SCHEMA_NONE,
|
||||
{
|
||||
{ "label", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
@@ -16,11 +17,12 @@ const SecretSchema *docker_get_schema(void)
|
||||
return &docker_schema;
|
||||
}
|
||||
|
||||
GError *add(char *server, char *username, char *secret) {
|
||||
GError *add(char *label, char *server, char *username, char *secret) {
|
||||
GError *err = NULL;
|
||||
|
||||
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
|
||||
server, secret, NULL, &err,
|
||||
"label", label,
|
||||
"server", server,
|
||||
"username", username,
|
||||
"docker_cli", "1",
|
||||
@@ -40,7 +42,7 @@ GError *delete(char *server) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *get_username(SecretItem *item) {
|
||||
char *get_attribute(const char *attribute, SecretItem *item) {
|
||||
GHashTable *attributes;
|
||||
GHashTableIter iter;
|
||||
gchar *value, *key;
|
||||
@@ -48,7 +50,7 @@ char *get_username(SecretItem *item) {
|
||||
attributes = secret_item_get_attributes(item);
|
||||
g_hash_table_iter_init(&iter, attributes);
|
||||
while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) {
|
||||
if (strncmp(key, "username", strlen(key)) == 0)
|
||||
if (strncmp(key, attribute, strlen(key)) == 0)
|
||||
return (char *)value;
|
||||
}
|
||||
g_hash_table_unref(attributes);
|
||||
@@ -71,7 +73,7 @@ GError *get(char *server, char **username, char **secret) {
|
||||
|
||||
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
|
||||
if (err == NULL) {
|
||||
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
|
||||
items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err);
|
||||
if (err == NULL) {
|
||||
for (l = items; l != NULL; l = g_list_next(l)) {
|
||||
value = secret_item_get_schema_name(l->data);
|
||||
@@ -85,7 +87,7 @@ GError *get(char *server, char **username, char **secret) {
|
||||
*secret = strdup(secret_value_get(secretValue, &length));
|
||||
secret_value_unref(secretValue);
|
||||
}
|
||||
*username = get_username(l->data);
|
||||
*username = get_attribute("username", l->data);
|
||||
}
|
||||
g_list_free_full(items, g_object_unref);
|
||||
}
|
||||
@@ -98,22 +100,30 @@ GError *get(char *server, char **username, char **secret) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GError *list(char *** paths, char *** accts, unsigned int *list_l) {
|
||||
GError *list(char *ref_label, char *** paths, char *** accts, unsigned int *list_l) {
|
||||
GList *items;
|
||||
GError *err = NULL;
|
||||
SecretService *service;
|
||||
SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
|
||||
GHashTable *attributes;
|
||||
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
|
||||
// List credentials with the right label only
|
||||
g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label));
|
||||
|
||||
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
|
||||
if (err != NULL) {
|
||||
return err;
|
||||
}
|
||||
|
||||
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
|
||||
int numKeys = g_list_length(items);
|
||||
if (err != NULL) {
|
||||
return err;
|
||||
}
|
||||
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
|
||||
char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys);
|
||||
char **tmp_accts = (char **) calloc(1,(int)sizeof(char *)*numKeys);
|
||||
|
||||
// items now contains our keys from the gnome keyring
|
||||
// we will now put it in our two lists to return it to go
|
||||
GList *current;
|
||||
@@ -121,21 +131,25 @@ GError *list(char *** paths, char *** accts, unsigned int *list_l) {
|
||||
for(current = items; current!=NULL; current = current->next) {
|
||||
char *pathTmp = secret_item_get_label(current->data);
|
||||
// you cannot have a key without a label in the gnome keyring
|
||||
char *acctTmp = get_username(current->data);
|
||||
char *acctTmp = get_attribute("username",current->data);
|
||||
if (acctTmp==NULL) {
|
||||
acctTmp = "account not defined";
|
||||
}
|
||||
char *path = (char *) malloc(strlen(pathTmp));
|
||||
char *acct = (char *) malloc(strlen(acctTmp));
|
||||
path = pathTmp;
|
||||
acct = acctTmp;
|
||||
(*paths)[listNumber] = (char *) malloc(sizeof(char)*(strlen(path)));
|
||||
memcpy((*paths)[listNumber], path, sizeof(char)*(strlen(path)));
|
||||
(*accts)[listNumber] = (char *) malloc(sizeof(char)*(strlen(acct)));
|
||||
memcpy((*accts)[listNumber], acct, sizeof(char)*(strlen(acct)));
|
||||
|
||||
tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1));
|
||||
tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1));
|
||||
|
||||
memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1));
|
||||
memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1));
|
||||
|
||||
listNumber = listNumber + 1;
|
||||
}
|
||||
*list_l = numKeys;
|
||||
|
||||
*paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber);
|
||||
*accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber);
|
||||
|
||||
*list_l = listNumber;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -144,5 +158,4 @@ void freeListData(char *** data, unsigned int length) {
|
||||
for(i=0; i<length; i++) {
|
||||
free((*data)[i]);
|
||||
}
|
||||
free(*data);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
|
||||
if creds == nil {
|
||||
return errors.New("missing credentials")
|
||||
}
|
||||
credsLabel := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabel))
|
||||
server := C.CString(creds.ServerURL)
|
||||
defer C.free(unsafe.Pointer(server))
|
||||
username := C.CString(creds.Username)
|
||||
@@ -29,7 +31,7 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
|
||||
secret := C.CString(creds.Secret)
|
||||
defer C.free(unsafe.Pointer(secret))
|
||||
|
||||
if err := C.add(server, username, secret); err != nil {
|
||||
if err := C.add(credsLabel, server, username, secret); err != nil {
|
||||
defer C.g_error_free(err)
|
||||
errMsg := (*C.char)(unsafe.Pointer(err.message))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
@@ -79,25 +81,36 @@ func (h Secretservice) Get(serverURL string) (string, string, error) {
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
// List returns the stored URLs and corresponding usernames.
|
||||
// List returns the stored URLs and corresponding usernames for a given credentials label
|
||||
func (h Secretservice) List() (map[string]string, error) {
|
||||
credsLabelC := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabelC))
|
||||
|
||||
var pathsC **C.char
|
||||
defer C.free(unsafe.Pointer(pathsC))
|
||||
var acctsC **C.char
|
||||
defer C.free(unsafe.Pointer(acctsC))
|
||||
var listLenC C.uint
|
||||
err := C.list(&pathsC, &acctsC, &listLenC)
|
||||
if err != nil {
|
||||
defer C.free(unsafe.Pointer(err))
|
||||
return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library")
|
||||
}
|
||||
err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC)
|
||||
defer C.freeListData(&pathsC, listLenC)
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
if err != nil {
|
||||
defer C.g_error_free(err)
|
||||
errMsg := (*C.char)(unsafe.Pointer(err.message))
|
||||
return nil, errors.New(C.GoString(errMsg))
|
||||
}
|
||||
|
||||
resp := make(map[string]string)
|
||||
|
||||
listLen := int(listLenC)
|
||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
resp := make(map[string]string)
|
||||
if listLen == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
// The maximum capacity of the following two slices is limited to (2^29)-1 to remain compatible
|
||||
// with 32-bit platforms. The size of a `*C.char` (a pointer) is 4 Byte on a 32-bit system
|
||||
// and (2^29)*4 == math.MaxInt32 + 1. -- See issue golang/go#13656
|
||||
pathTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
for i := 0; i < listLen; i++ {
|
||||
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
|
||||
|
||||
#define DOCKER_SCHEMA docker_get_schema()
|
||||
|
||||
GError *add(char *server, char *username, char *secret);
|
||||
GError *add(char *label, char *server, char *username, char *secret);
|
||||
GError *delete(char *server);
|
||||
GError *get(char *server, char **username, char **secret);
|
||||
GError *list(char *** paths, char *** accts, unsigned int *list_l);
|
||||
GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l);
|
||||
void freeListData(char *** data, unsigned int length);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package secretservice
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
@@ -16,10 +17,36 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
}
|
||||
|
||||
helper := Secretservice{}
|
||||
|
||||
// Check how many docker credentials we have when starting the test
|
||||
oldAuths, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// If any docker credentials with the tests values we are providing, we
|
||||
// remove them as they probably come from a previous failed test
|
||||
for k, v := range oldAuths {
|
||||
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check again how many docker credentials we have when starting the test
|
||||
oldAuths, err = helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add new credentials
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify that it is inside the secret service store
|
||||
username, secret, err := helper.Get(creds.ServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -33,15 +60,21 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
// We should have one more credential than before adding
|
||||
newAuths, err := helper.List()
|
||||
if err != nil || (len(newAuths)-len(oldAuths) != 1) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldAuths = newAuths
|
||||
|
||||
// Deleting the credentials associated to current server url should succeed
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
auths, err := helper.List()
|
||||
if err != nil || len(auths) == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
helper.Add(creds)
|
||||
if newauths, err := helper.List(); (len(newauths) - len(auths)) != 1 {
|
||||
|
||||
// We should have one less credential than before deleting
|
||||
newAuths, err = helper.List()
|
||||
if err != nil || (len(oldAuths)-len(newAuths) != 1) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.go text eol=lf
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
||||
coverage.txt
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
wincred
|
||||
=======
|
||||
|
||||
Go wrapper around the Windows Credential Manager API functions.
|
||||
|
||||
[](https://github.com/danieljoos/wincred/releases/latest)
|
||||
[](https://github.com/danieljoos/wincred/actions?query=workflow%3Atest)
|
||||
[](https://goreportcard.com/report/github.com/danieljoos/wincred)
|
||||
[](https://codecov.io/gh/danieljoos/wincred)
|
||||
[](https://pkg.go.dev/github.com/danieljoos/wincred)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
```Go
|
||||
go get github.com/danieljoos/wincred
|
||||
```
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
See the following examples:
|
||||
|
||||
### Create and store a new generic credential object
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danieljoos/wincred"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cred := wincred.NewGenericCredential("myGoApplication")
|
||||
cred.CredentialBlob = []byte("my secret")
|
||||
err := cred.Write()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve a credential object
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danieljoos/wincred"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cred, err := wincred.GetGenericCredential("myGoApplication")
|
||||
if err == nil {
|
||||
fmt.Println(string(cred.CredentialBlob))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Remove a credential object
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danieljoos/wincred"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cred, err := wincred.GetGenericCredential("myGoApplication")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
cred.Delete()
|
||||
}
|
||||
```
|
||||
|
||||
### List all available credentials
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danieljoos/wincred"
|
||||
)
|
||||
|
||||
func main() {
|
||||
creds, err := wincred.List()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
for i := range(creds) {
|
||||
fmt.Println(creds[i].TargetName)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Hints
|
||||
-----
|
||||
|
||||
### Encoding
|
||||
|
||||
The credential objects simply store byte arrays without specific meaning or encoding.
|
||||
For sharing between different applications, it might make sense to apply an explicit string encoding - for example **UTF-16 LE** (used nearly everywhere in the Win32 API).
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/danieljoos/wincred"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cred := wincred.NewGenericCredential("myGoApplication")
|
||||
|
||||
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||
blob, _, err := transform.Bytes(encoder, []byte("mysecret"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cred.CredentialBlob = blob
|
||||
err = cred.Write()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
The size of a credential blob is limited to **2560 Bytes** by the Windows API.
|
||||
+40
-45
@@ -1,88 +1,83 @@
|
||||
// +build windows
|
||||
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var nullPointer = unsafe.Pointer(uintptr(0))
|
||||
|
||||
// Create a Go string using a pointer to a zero-terminated UTF 16 encoded string.
|
||||
// See github.com/AllenDang/w32
|
||||
func utf16PtrToString(wstr *uint16) string {
|
||||
if wstr != nil {
|
||||
buf := make([]uint16, 0, 256)
|
||||
for ptr := uintptr(unsafe.Pointer(wstr)); ; ptr += 2 {
|
||||
rune := *(*uint16)(unsafe.Pointer(ptr))
|
||||
if rune == 0 {
|
||||
return string(utf16.Decode(buf))
|
||||
}
|
||||
buf = append(buf, rune)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Create a byte array from a given UTF 16 char array
|
||||
// utf16ToByte creates a byte array from a given UTF 16 char array.
|
||||
func utf16ToByte(wstr []uint16) (result []byte) {
|
||||
result = make([]byte, len(wstr)*2)
|
||||
for i, _ := range wstr {
|
||||
for i := range wstr {
|
||||
binary.LittleEndian.PutUint16(result[(i*2):(i*2)+2], wstr[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Copies the given C byte array to a Go byte array (see `C.GoBytes`)
|
||||
func goBytes(src unsafe.Pointer, len uint32) []byte {
|
||||
if src == nullPointer {
|
||||
// utf16FromString creates a UTF16 char array from a string.
|
||||
func utf16FromString(str string) []uint16 {
|
||||
res, err := syscall.UTF16FromString(str)
|
||||
if err != nil {
|
||||
return []uint16{}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`).
|
||||
// This function avoids having cgo as dependency.
|
||||
func goBytes(src uintptr, len uint32) []byte {
|
||||
if src == uintptr(0) {
|
||||
return []byte{}
|
||||
}
|
||||
slice := (*[1 << 30]byte)(src)[0:len]
|
||||
rv := make([]byte, len)
|
||||
copy(rv, slice)
|
||||
return rv[:]
|
||||
copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: src,
|
||||
Len: int(len),
|
||||
Cap: int(len),
|
||||
})))
|
||||
return rv
|
||||
}
|
||||
|
||||
// Convert the given CREDENTIAL struct to a more usable structure
|
||||
func nativeToCredential(cred *nativeCREDENTIAL) (result *Credential) {
|
||||
if unsafe.Pointer(cred) == nullPointer {
|
||||
func sysToCredential(cred *sysCREDENTIAL) (result *Credential) {
|
||||
if cred == nil {
|
||||
return nil
|
||||
}
|
||||
result = new(Credential)
|
||||
result.Comment = utf16PtrToString(cred.Comment)
|
||||
result.TargetName = utf16PtrToString(cred.TargetName)
|
||||
result.TargetAlias = utf16PtrToString(cred.TargetAlias)
|
||||
result.UserName = utf16PtrToString(cred.UserName)
|
||||
result.Comment = syscall.UTF16PtrToString(cred.Comment)
|
||||
result.TargetName = syscall.UTF16PtrToString(cred.TargetName)
|
||||
result.TargetAlias = syscall.UTF16PtrToString(cred.TargetAlias)
|
||||
result.UserName = syscall.UTF16PtrToString(cred.UserName)
|
||||
result.LastWritten = time.Unix(0, cred.LastWritten.Nanoseconds())
|
||||
result.Persist = CredentialPersistence(cred.Persist)
|
||||
result.CredentialBlob = goBytes(unsafe.Pointer(cred.CredentialBlob), cred.CredentialBlobSize)
|
||||
result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize)
|
||||
result.Attributes = make([]CredentialAttribute, cred.AttributeCount)
|
||||
attrSliceHeader := reflect.SliceHeader{
|
||||
attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: cred.Attributes,
|
||||
Len: int(cred.AttributeCount),
|
||||
Cap: int(cred.AttributeCount),
|
||||
}
|
||||
attrSlice := *(*[]nativeCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&attrSliceHeader))
|
||||
}))
|
||||
for i, attr := range attrSlice {
|
||||
resultAttr := &result.Attributes[i]
|
||||
resultAttr.Keyword = utf16PtrToString(attr.Keyword)
|
||||
resultAttr.Value = goBytes(unsafe.Pointer(attr.Value), attr.ValueSize)
|
||||
resultAttr.Keyword = syscall.UTF16PtrToString(attr.Keyword)
|
||||
resultAttr.Value = goBytes(attr.Value, attr.ValueSize)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Convert the given Credential object back to a CREDENTIAL struct, which can be used for calling the
|
||||
// Windows APIs
|
||||
func nativeFromCredential(cred *Credential) (result *nativeCREDENTIAL) {
|
||||
func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) {
|
||||
if cred == nil {
|
||||
return nil
|
||||
}
|
||||
result = new(nativeCREDENTIAL)
|
||||
result = new(sysCREDENTIAL)
|
||||
result.Flags = 0
|
||||
result.Type = 0
|
||||
result.TargetName, _ = syscall.UTF16PtrFromString(cred.TargetName)
|
||||
@@ -96,13 +91,13 @@ func nativeFromCredential(cred *Credential) (result *nativeCREDENTIAL) {
|
||||
}
|
||||
result.Persist = uint32(cred.Persist)
|
||||
result.AttributeCount = uint32(len(cred.Attributes))
|
||||
attributes := make([]nativeCREDENTIAL_ATTRIBUTE, len(cred.Attributes))
|
||||
attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes))
|
||||
if len(attributes) > 0 {
|
||||
result.Attributes = uintptr(unsafe.Pointer(&attributes[0]))
|
||||
} else {
|
||||
result.Attributes = 0
|
||||
}
|
||||
for i, _ := range cred.Attributes {
|
||||
for i := range cred.Attributes {
|
||||
inAttr := &cred.Attributes[i]
|
||||
outAttr := &attributes[i]
|
||||
outAttr.Keyword, _ = syscall.UTF16PtrFromString(inAttr.Keyword)
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// +build !windows
|
||||
|
||||
package wincred
|
||||
|
||||
func utf16ToByte(...interface{}) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func utf16FromString(...interface{}) []uint16 {
|
||||
return nil
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
|
||||
procCredRead proc = modadvapi32.NewProc("CredReadW")
|
||||
procCredWrite proc = modadvapi32.NewProc("CredWriteW")
|
||||
procCredDelete proc = modadvapi32.NewProc("CredDeleteW")
|
||||
procCredFree proc = modadvapi32.NewProc("CredFree")
|
||||
procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW")
|
||||
)
|
||||
|
||||
// Interface for syscall.Proc: helps testing
|
||||
type proc interface {
|
||||
Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
|
||||
type nativeCREDENTIAL struct {
|
||||
Flags uint32
|
||||
Type uint32
|
||||
TargetName *uint16
|
||||
Comment *uint16
|
||||
LastWritten syscall.Filetime
|
||||
CredentialBlobSize uint32
|
||||
CredentialBlob uintptr
|
||||
Persist uint32
|
||||
AttributeCount uint32
|
||||
Attributes uintptr
|
||||
TargetAlias *uint16
|
||||
UserName *uint16
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374790(v=vs.85).aspx
|
||||
type nativeCREDENTIAL_ATTRIBUTE struct {
|
||||
Keyword *uint16
|
||||
Flags uint32
|
||||
ValueSize uint32
|
||||
Value uintptr
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
|
||||
type nativeCRED_TYPE uint32
|
||||
|
||||
const (
|
||||
naCRED_TYPE_GENERIC nativeCRED_TYPE = 0x1
|
||||
naCRED_TYPE_DOMAIN_PASSWORD nativeCRED_TYPE = 0x2
|
||||
naCRED_TYPE_DOMAIN_CERTIFICATE nativeCRED_TYPE = 0x3
|
||||
naCRED_TYPE_DOMAIN_VISIBLE_PASSWORD nativeCRED_TYPE = 0x4
|
||||
naCRED_TYPE_GENERIC_CERTIFICATE nativeCRED_TYPE = 0x5
|
||||
naCRED_TYPE_DOMAIN_EXTENDED nativeCRED_TYPE = 0x6
|
||||
|
||||
naERROR_NOT_FOUND = "Element not found."
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374804(v=vs.85).aspx
|
||||
func nativeCredRead(targetName string, typ nativeCRED_TYPE) (*Credential, error) {
|
||||
var pcred uintptr
|
||||
targetNamePtr, _ := syscall.UTF16PtrFromString(targetName)
|
||||
ret, _, err := procCredRead.Call(
|
||||
uintptr(unsafe.Pointer(targetNamePtr)),
|
||||
uintptr(typ),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&pcred)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer procCredFree.Call(pcred)
|
||||
|
||||
return nativeToCredential((*nativeCREDENTIAL)(unsafe.Pointer(pcred))), nil
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375187(v=vs.85).aspx
|
||||
func nativeCredWrite(cred *Credential, typ nativeCRED_TYPE) error {
|
||||
ncred := nativeFromCredential(cred)
|
||||
ncred.Type = uint32(typ)
|
||||
ret, _, err := procCredWrite.Call(
|
||||
uintptr(unsafe.Pointer(ncred)),
|
||||
0,
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374787(v=vs.85).aspx
|
||||
func nativeCredDelete(cred *Credential, typ nativeCRED_TYPE) error {
|
||||
targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName)
|
||||
ret, _, err := procCredDelete.Call(
|
||||
uintptr(unsafe.Pointer(targetNamePtr)),
|
||||
uintptr(typ),
|
||||
0,
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa374794(v=vs.85).aspx
|
||||
func nativeCredEnumerate(filter string, all bool) ([]*Credential, error) {
|
||||
var count int
|
||||
var pcreds uintptr
|
||||
var filterPtr uintptr
|
||||
if !all {
|
||||
filterUtf16Ptr, _ := syscall.UTF16PtrFromString(filter)
|
||||
filterPtr = uintptr(unsafe.Pointer(filterUtf16Ptr))
|
||||
} else {
|
||||
filterPtr = 0
|
||||
}
|
||||
ret, _, err := procCredEnumerate.Call(
|
||||
filterPtr,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
uintptr(unsafe.Pointer(&pcreds)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer procCredFree.Call(pcreds)
|
||||
pcredsSlice := (*[1 << 30]uintptr)(unsafe.Pointer(pcreds))[:count:count]
|
||||
creds := make([]*Credential, count)
|
||||
for i := range creds {
|
||||
creds[i] = nativeToCredential((*nativeCREDENTIAL)(unsafe.Pointer(pcredsSlice[i])))
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
// +build windows
|
||||
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
procCredRead proc = modadvapi32.NewProc("CredReadW")
|
||||
procCredWrite proc = modadvapi32.NewProc("CredWriteW")
|
||||
procCredDelete proc = modadvapi32.NewProc("CredDeleteW")
|
||||
procCredFree proc = modadvapi32.NewProc("CredFree")
|
||||
procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW")
|
||||
)
|
||||
|
||||
// Interface for syscall.Proc: helps testing
|
||||
type proc interface {
|
||||
Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
|
||||
type sysCREDENTIAL struct {
|
||||
Flags uint32
|
||||
Type uint32
|
||||
TargetName *uint16
|
||||
Comment *uint16
|
||||
LastWritten syscall.Filetime
|
||||
CredentialBlobSize uint32
|
||||
CredentialBlob uintptr
|
||||
Persist uint32
|
||||
AttributeCount uint32
|
||||
Attributes uintptr
|
||||
TargetAlias *uint16
|
||||
UserName *uint16
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributew
|
||||
type sysCREDENTIAL_ATTRIBUTE struct {
|
||||
Keyword *uint16
|
||||
Flags uint32
|
||||
ValueSize uint32
|
||||
Value uintptr
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
|
||||
type sysCRED_TYPE uint32
|
||||
|
||||
const (
|
||||
sysCRED_TYPE_GENERIC sysCRED_TYPE = 0x1
|
||||
sysCRED_TYPE_DOMAIN_PASSWORD sysCRED_TYPE = 0x2
|
||||
sysCRED_TYPE_DOMAIN_CERTIFICATE sysCRED_TYPE = 0x3
|
||||
sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4
|
||||
sysCRED_TYPE_GENERIC_CERTIFICATE sysCRED_TYPE = 0x5
|
||||
sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
|
||||
sysERROR_NOT_FOUND = syscall.Errno(1168)
|
||||
sysERROR_INVALID_PARAMETER = syscall.Errno(87)
|
||||
)
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw
|
||||
func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) {
|
||||
var pcred *sysCREDENTIAL
|
||||
targetNamePtr, _ := syscall.UTF16PtrFromString(targetName)
|
||||
ret, _, err := procCredRead.Call(
|
||||
uintptr(unsafe.Pointer(targetNamePtr)),
|
||||
uintptr(typ),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&pcred)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer procCredFree.Call(uintptr(unsafe.Pointer(pcred)))
|
||||
|
||||
return sysToCredential(pcred), nil
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credwritew
|
||||
func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error {
|
||||
ncred := sysFromCredential(cred)
|
||||
ncred.Type = uint32(typ)
|
||||
ret, _, err := procCredWrite.Call(
|
||||
uintptr(unsafe.Pointer(ncred)),
|
||||
0,
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew
|
||||
func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error {
|
||||
targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName)
|
||||
ret, _, err := procCredDelete.Call(
|
||||
uintptr(unsafe.Pointer(targetNamePtr)),
|
||||
uintptr(typ),
|
||||
0,
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credenumeratew
|
||||
func sysCredEnumerate(filter string, all bool) ([]*Credential, error) {
|
||||
var count int
|
||||
var pcreds uintptr
|
||||
var filterPtr *uint16
|
||||
if !all {
|
||||
filterPtr, _ = syscall.UTF16PtrFromString(filter)
|
||||
}
|
||||
ret, _, err := procCredEnumerate.Call(
|
||||
uintptr(unsafe.Pointer(filterPtr)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
uintptr(unsafe.Pointer(&pcreds)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer procCredFree.Call(pcreds)
|
||||
credsSlice := *(*[]*sysCREDENTIAL)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: pcreds,
|
||||
Len: count,
|
||||
Cap: count,
|
||||
}))
|
||||
creds := make([]*Credential, count, count)
|
||||
for i, cred := range credsSlice {
|
||||
creds[i] = sysToCredential(cred)
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// +build !windows
|
||||
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
sysCRED_TYPE_GENERIC = 0
|
||||
sysCRED_TYPE_DOMAIN_PASSWORD = 0
|
||||
sysCRED_TYPE_DOMAIN_CERTIFICATE = 0
|
||||
sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0
|
||||
sysCRED_TYPE_GENERIC_CERTIFICATE = 0
|
||||
sysCRED_TYPE_DOMAIN_EXTENDED = 0
|
||||
|
||||
sysERROR_NOT_FOUND = syscall.Errno(1)
|
||||
sysERROR_INVALID_PARAMETER = syscall.Errno(1)
|
||||
)
|
||||
|
||||
func sysCredRead(...interface{}) (*Credential, error) {
|
||||
return nil, errors.New("Operation not supported")
|
||||
}
|
||||
|
||||
func sysCredWrite(...interface{}) error {
|
||||
return errors.New("Operation not supported")
|
||||
}
|
||||
|
||||
func sysCredDelete(...interface{}) error {
|
||||
return errors.New("Operation not supported")
|
||||
}
|
||||
|
||||
func sysCredEnumerate(...interface{}) ([]*Credential, error) {
|
||||
return nil, errors.New("Operation not supported")
|
||||
}
|
||||
+34
-2
@@ -4,19 +4,38 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CredentialPersistence describes one of three persistence modes of a credential.
|
||||
// A detailed description of the available modes can be found on
|
||||
// Docs: https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
|
||||
type CredentialPersistence uint32
|
||||
|
||||
const (
|
||||
PersistSession CredentialPersistence = 0x1
|
||||
// PersistSession indicates that the credential only persists for the life
|
||||
// of the current Windows login session. Such a credential is not visible in
|
||||
// any other logon session, even from the same user.
|
||||
PersistSession CredentialPersistence = 0x1
|
||||
|
||||
// PersistLocalMachine indicates that the credential persists for this and
|
||||
// all subsequent logon sessions on this local machine/computer. It is
|
||||
// however not visible for logon sessions of this user on a different
|
||||
// machine.
|
||||
PersistLocalMachine CredentialPersistence = 0x2
|
||||
PersistEnterprise CredentialPersistence = 0x3
|
||||
|
||||
// PersistEnterprise indicates that the credential persists for this and all
|
||||
// subsequent logon sessions for this user. It is also visible for logon
|
||||
// sessions on different computers.
|
||||
PersistEnterprise CredentialPersistence = 0x3
|
||||
)
|
||||
|
||||
// CredentialAttribute represents an application-specific attribute of a credential.
|
||||
type CredentialAttribute struct {
|
||||
Keyword string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// Credential is the basic credential structure.
|
||||
// A credential is identified by its target name.
|
||||
// The actual credential secret is available in the CredentialBlob field.
|
||||
type Credential struct {
|
||||
TargetName string
|
||||
Comment string
|
||||
@@ -28,10 +47,23 @@ type Credential struct {
|
||||
Persist CredentialPersistence
|
||||
}
|
||||
|
||||
// GenericCredential holds a credential for generic usage.
|
||||
// It is typically defined and used by applications that need to manage user
|
||||
// secrets.
|
||||
//
|
||||
// More information about the available kinds of credentials of the Windows
|
||||
// Credential Management API can be found on Docs:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials
|
||||
type GenericCredential struct {
|
||||
Credential
|
||||
}
|
||||
|
||||
// DomainPassword holds a domain credential that is typically used by the
|
||||
// operating system for user logon.
|
||||
//
|
||||
// More information about the available kinds of credentials of the Windows
|
||||
// Credential Management API can be found on Docs:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials
|
||||
type DomainPassword struct {
|
||||
Credential
|
||||
}
|
||||
|
||||
+55
-24
@@ -1,19 +1,36 @@
|
||||
// Package wincred provides primitives for accessing the Windows Credentials Management API.
|
||||
// This includes functions for retrieval, listing and storage of credentials as well as Go structures for convenient access to the credential data.
|
||||
//
|
||||
// A more detailed description of Windows Credentials Management can be found on
|
||||
// Docs: https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/credentials-management
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
// ErrElementNotFound is the error that is returned if a requested element cannot be found.
|
||||
// This error constant can be used to check if a credential could not be found.
|
||||
ErrElementNotFound = sysERROR_NOT_FOUND
|
||||
|
||||
// ErrInvalidParameter is the error that is returned for invalid parameters.
|
||||
// This error constant can be used to check if the given function parameters were invalid.
|
||||
// For example when trying to create a new generic credential with an empty target name.
|
||||
ErrInvalidParameter = sysERROR_INVALID_PARAMETER
|
||||
)
|
||||
|
||||
// Get the generic credential with the given name from Windows credential manager
|
||||
// GetGenericCredential fetches the generic credential with the given name from Windows credential manager.
|
||||
// It returns nil and an error if the credential could not be found or an error occurred.
|
||||
func GetGenericCredential(targetName string) (*GenericCredential, error) {
|
||||
cred, err := nativeCredRead(targetName, naCRED_TYPE_GENERIC)
|
||||
cred, err := sysCredRead(targetName, sysCRED_TYPE_GENERIC)
|
||||
if cred != nil {
|
||||
return &GenericCredential{*cred}, err
|
||||
return &GenericCredential{Credential: *cred}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new generic credential with the given name
|
||||
// NewGenericCredential creates a new generic credential object with the given name.
|
||||
// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage.
|
||||
// The credential object is NOT yet persisted to the Windows credential vault.
|
||||
func NewGenericCredential(targetName string) (result *GenericCredential) {
|
||||
result = new(GenericCredential)
|
||||
result.TargetName = targetName
|
||||
@@ -21,28 +38,31 @@ func NewGenericCredential(targetName string) (result *GenericCredential) {
|
||||
return
|
||||
}
|
||||
|
||||
// Persist the credential to Windows credential manager
|
||||
// Write persists the generic credential object to Windows credential manager.
|
||||
func (t *GenericCredential) Write() (err error) {
|
||||
err = nativeCredWrite(&t.Credential, naCRED_TYPE_GENERIC)
|
||||
err = sysCredWrite(&t.Credential, sysCRED_TYPE_GENERIC)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the credential from Windows credential manager
|
||||
// Delete removes the credential object from Windows credential manager.
|
||||
func (t *GenericCredential) Delete() (err error) {
|
||||
err = nativeCredDelete(&t.Credential, naCRED_TYPE_GENERIC)
|
||||
err = sysCredDelete(&t.Credential, sysCRED_TYPE_GENERIC)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the domain password credential with the given target host name
|
||||
// GetDomainPassword fetches the domain-password credential with the given target host name from Windows credential manager.
|
||||
// It returns nil and an error if the credential could not be found or an error occurred.
|
||||
func GetDomainPassword(targetName string) (*DomainPassword, error) {
|
||||
cred, err := nativeCredRead(targetName, naCRED_TYPE_DOMAIN_PASSWORD)
|
||||
cred, err := sysCredRead(targetName, sysCRED_TYPE_DOMAIN_PASSWORD)
|
||||
if cred != nil {
|
||||
return &DomainPassword{*cred}, err
|
||||
return &DomainPassword{Credential: *cred}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new domain password credential used for login to the given target host name
|
||||
// NewDomainPassword creates a new domain-password credential used for login to the given target host name.
|
||||
// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage.
|
||||
// The credential object is NOT yet persisted to the Windows credential vault.
|
||||
func NewDomainPassword(targetName string) (result *DomainPassword) {
|
||||
result = new(DomainPassword)
|
||||
result.TargetName = targetName
|
||||
@@ -50,28 +70,39 @@ func NewDomainPassword(targetName string) (result *DomainPassword) {
|
||||
return
|
||||
}
|
||||
|
||||
// Persist the domain password credential to Windows credential manager
|
||||
// Write persists the domain-password credential to Windows credential manager.
|
||||
func (t *DomainPassword) Write() (err error) {
|
||||
err = nativeCredWrite(&t.Credential, naCRED_TYPE_DOMAIN_PASSWORD)
|
||||
err = sysCredWrite(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the domain password credential from Windows credential manager
|
||||
// Delete removes the domain-password credential from Windows credential manager.
|
||||
func (t *DomainPassword) Delete() (err error) {
|
||||
err = nativeCredDelete(&t.Credential, naCRED_TYPE_DOMAIN_PASSWORD)
|
||||
err = sysCredDelete(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the CredentialBlob field of a domain password credential
|
||||
// using an UTF16 encoded password string
|
||||
// SetPassword sets the CredentialBlob field of a domain password credential to the given string.
|
||||
func (t *DomainPassword) SetPassword(pw string) {
|
||||
t.CredentialBlob = utf16ToByte(syscall.StringToUTF16(pw))
|
||||
t.CredentialBlob = utf16ToByte(utf16FromString(pw))
|
||||
}
|
||||
|
||||
// List the contents of the Credentials store
|
||||
// List retrieves all credentials of the Credentials store.
|
||||
func List() ([]*Credential, error) {
|
||||
creds, err := nativeCredEnumerate("", true)
|
||||
if err != nil && err.Error() == naERROR_NOT_FOUND {
|
||||
creds, err := sysCredEnumerate("", true)
|
||||
if err != nil && errors.Is(err, ErrElementNotFound) {
|
||||
// Ignore ERROR_NOT_FOUND and return an empty list instead
|
||||
creds = []*Credential{}
|
||||
err = nil
|
||||
}
|
||||
return creds, err
|
||||
}
|
||||
|
||||
// FilteredList retrieves the list of credentials from the Credentials store that match the given filter.
|
||||
// The filter string defines the prefix followed by an asterisk for the `TargetName` attribute of the credentials.
|
||||
func FilteredList(filter string) ([]*Credential, error) {
|
||||
creds, err := sysCredEnumerate(filter, false)
|
||||
if err != nil && errors.Is(err, ErrElementNotFound) {
|
||||
// Ignore ERROR_NOT_FOUND and return an empty list instead
|
||||
creds = []*Credential{}
|
||||
err = nil
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package execabs is a drop-in replacement for os/exec
|
||||
// that requires PATH lookups to find absolute paths.
|
||||
// That is, execabs.Command("cmd") runs the same PATH lookup
|
||||
// as exec.Command("cmd"), but if the result is a path
|
||||
// which is relative, the Run and Start methods will report
|
||||
// an error instead of running the executable.
|
||||
//
|
||||
// See https://blog.golang.org/path-security for more information
|
||||
// about when it may be necessary or appropriate to use this package.
|
||||
package execabs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
// It is an alias for exec.ErrNotFound.
|
||||
var ErrNotFound = exec.ErrNotFound
|
||||
|
||||
// Cmd represents an external command being prepared or run.
|
||||
// It is an alias for exec.Cmd.
|
||||
type Cmd = exec.Cmd
|
||||
|
||||
// Error is returned by LookPath when it fails to classify a file as an executable.
|
||||
// It is an alias for exec.Error.
|
||||
type Error = exec.Error
|
||||
|
||||
// An ExitError reports an unsuccessful exit by a command.
|
||||
// It is an alias for exec.ExitError.
|
||||
type ExitError = exec.ExitError
|
||||
|
||||
func relError(file, path string) error {
|
||||
return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
|
||||
}
|
||||
|
||||
// LookPath searches for an executable named file in the directories
|
||||
// named by the PATH environment variable. If file contains a slash,
|
||||
// it is tried directly and the PATH is not consulted. The result will be
|
||||
// an absolute path.
|
||||
//
|
||||
// LookPath differs from exec.LookPath in its handling of PATH lookups,
|
||||
// which are used for file names without slashes. If exec.LookPath's
|
||||
// PATH lookup would have returned an executable from the current directory,
|
||||
// LookPath instead returns an error.
|
||||
func LookPath(file string) (string, error) {
|
||||
path, err := exec.LookPath(file)
|
||||
if err != nil && !isGo119ErrDot(err) {
|
||||
return "", err
|
||||
}
|
||||
if filepath.Base(file) == file && !filepath.IsAbs(path) {
|
||||
return "", relError(file, path)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func fixCmd(name string, cmd *exec.Cmd) {
|
||||
if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
|
||||
// exec.Command was called with a bare binary name and
|
||||
// exec.LookPath returned a path which is not absolute.
|
||||
// Set cmd.lookPathErr and clear cmd.Path so that it
|
||||
// cannot be run.
|
||||
lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
|
||||
if *lookPathErr == nil {
|
||||
*lookPathErr = relError(name, cmd.Path)
|
||||
}
|
||||
cmd.Path = ""
|
||||
}
|
||||
}
|
||||
|
||||
// CommandContext is like Command but includes a context.
|
||||
//
|
||||
// The provided context is used to kill the process (by calling os.Process.Kill)
|
||||
// if the context becomes done before the command completes on its own.
|
||||
func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, name, arg...)
|
||||
fixCmd(name, cmd)
|
||||
return cmd
|
||||
|
||||
}
|
||||
|
||||
// Command returns the Cmd struct to execute the named program with the given arguments.
|
||||
// See exec.Command for most details.
|
||||
//
|
||||
// Command differs from exec.Command in its handling of PATH lookups,
|
||||
// which are used when the program name contains no slashes.
|
||||
// If exec.Command would have returned an exec.Cmd configured to run an
|
||||
// executable from the current directory, Command instead
|
||||
// returns an exec.Cmd that will return an error from Start or Run.
|
||||
func Command(name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
fixCmd(name, cmd)
|
||||
return cmd
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.19
|
||||
// +build !go1.19
|
||||
|
||||
package execabs
|
||||
|
||||
func isGo119ErrDot(err error) bool {
|
||||
return false
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package execabs
|
||||
|
||||
import "strings"
|
||||
|
||||
func isGo119ErrDot(err error) bool {
|
||||
// TODO: return errors.Is(err, exec.ErrDot)
|
||||
return strings.Contains(err.Error(), "current directory")
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package unsafeheader contains header declarations for the Go runtime's
|
||||
// slice and string implementations.
|
||||
//
|
||||
// This package allows x/sys to use types equivalent to
|
||||
// reflect.SliceHeader and reflect.StringHeader without introducing
|
||||
// a dependency on the (relatively heavy) "reflect" package.
|
||||
package unsafeheader
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Slice is the runtime representation of a slice.
|
||||
// It cannot be used safely or portably and its representation may change in a later release.
|
||||
type Slice struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
|
||||
// String is the runtime representation of a string.
|
||||
// It cannot be used safely or portably and its representation may change in a later release.
|
||||
type String struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && go1.9
|
||||
// +build windows,go1.9
|
||||
|
||||
package windows
|
||||
|
||||
import "syscall"
|
||||
|
||||
type Errno = syscall.Errno
|
||||
type SysProcAttr = syscall.SysProcAttr
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// We need to use LoadLibrary and GetProcAddress from the Go runtime, because
|
||||
// the these symbols are loaded by the system linker and are required to
|
||||
// dynamically load additional symbols. Note that in the Go runtime, these
|
||||
// return syscall.Handle and syscall.Errno, but these are the same, in fact,
|
||||
// as windows.Handle and windows.Errno, and we intend to keep these the same.
|
||||
|
||||
//go:linkname syscall_loadlibrary syscall.loadlibrary
|
||||
func syscall_loadlibrary(filename *uint16) (handle Handle, err Errno)
|
||||
|
||||
//go:linkname syscall_getprocaddress syscall.getprocaddress
|
||||
func syscall_getprocaddress(handle Handle, procname *uint8) (proc uintptr, err Errno)
|
||||
|
||||
// DLLError describes reasons for DLL load failures.
|
||||
type DLLError struct {
|
||||
Err error
|
||||
ObjName string
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *DLLError) Error() string { return e.Msg }
|
||||
|
||||
func (e *DLLError) Unwrap() error { return e.Err }
|
||||
|
||||
// A DLL implements access to a single DLL.
|
||||
type DLL struct {
|
||||
Name string
|
||||
Handle Handle
|
||||
}
|
||||
|
||||
// LoadDLL loads DLL file into memory.
|
||||
//
|
||||
// Warning: using LoadDLL without an absolute path name is subject to
|
||||
// DLL preloading attacks. To safely load a system DLL, use LazyDLL
|
||||
// with System set to true, or use LoadLibraryEx directly.
|
||||
func LoadDLL(name string) (dll *DLL, err error) {
|
||||
namep, err := UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, e := syscall_loadlibrary(namep)
|
||||
if e != 0 {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to load " + name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
d := &DLL{
|
||||
Name: name,
|
||||
Handle: h,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// MustLoadDLL is like LoadDLL but panics if load operation failes.
|
||||
func MustLoadDLL(name string) *DLL {
|
||||
d, e := LoadDLL(name)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// FindProc searches DLL d for procedure named name and returns *Proc
|
||||
// if found. It returns an error if search fails.
|
||||
func (d *DLL) FindProc(name string) (proc *Proc, err error) {
|
||||
namep, err := BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, e := syscall_getprocaddress(d.Handle, namep)
|
||||
if e != 0 {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
p := &Proc{
|
||||
Dll: d,
|
||||
Name: name,
|
||||
addr: a,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// MustFindProc is like FindProc but panics if search fails.
|
||||
func (d *DLL) MustFindProc(name string) *Proc {
|
||||
p, e := d.FindProc(name)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// FindProcByOrdinal searches DLL d for procedure by ordinal and returns *Proc
|
||||
// if found. It returns an error if search fails.
|
||||
func (d *DLL) FindProcByOrdinal(ordinal uintptr) (proc *Proc, err error) {
|
||||
a, e := GetProcAddressByOrdinal(d.Handle, ordinal)
|
||||
name := "#" + itoa(int(ordinal))
|
||||
if e != nil {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
p := &Proc{
|
||||
Dll: d,
|
||||
Name: name,
|
||||
addr: a,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// MustFindProcByOrdinal is like FindProcByOrdinal but panics if search fails.
|
||||
func (d *DLL) MustFindProcByOrdinal(ordinal uintptr) *Proc {
|
||||
p, e := d.FindProcByOrdinal(ordinal)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Release unloads DLL d from memory.
|
||||
func (d *DLL) Release() (err error) {
|
||||
return FreeLibrary(d.Handle)
|
||||
}
|
||||
|
||||
// A Proc implements access to a procedure inside a DLL.
|
||||
type Proc struct {
|
||||
Dll *DLL
|
||||
Name string
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
// Addr returns the address of the procedure represented by p.
|
||||
// The return value can be passed to Syscall to run the procedure.
|
||||
func (p *Proc) Addr() uintptr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||
// are supplied.
|
||||
//
|
||||
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||
// Callers must inspect the primary return value to decide whether an error occurred
|
||||
// (according to the semantics of the specific function being called) before consulting
|
||||
// the error. The error will be guaranteed to contain windows.Errno.
|
||||
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||
switch len(a) {
|
||||
case 0:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
|
||||
case 1:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
|
||||
case 2:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
|
||||
case 3:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
|
||||
case 4:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
|
||||
case 5:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
|
||||
case 6:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
|
||||
case 7:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
|
||||
case 8:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
|
||||
case 9:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
|
||||
case 10:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
|
||||
case 11:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
|
||||
case 12:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
|
||||
case 13:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
|
||||
case 14:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
|
||||
case 15:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
|
||||
default:
|
||||
panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
|
||||
}
|
||||
}
|
||||
|
||||
// A LazyDLL implements access to a single DLL.
|
||||
// It will delay the load of the DLL until the first
|
||||
// call to its Handle method or to one of its
|
||||
// LazyProc's Addr method.
|
||||
type LazyDLL struct {
|
||||
Name string
|
||||
|
||||
// System determines whether the DLL must be loaded from the
|
||||
// Windows System directory, bypassing the normal DLL search
|
||||
// path.
|
||||
System bool
|
||||
|
||||
mu sync.Mutex
|
||||
dll *DLL // non nil once DLL is loaded
|
||||
}
|
||||
|
||||
// Load loads DLL file d.Name into memory. It returns an error if fails.
|
||||
// Load will not try to load DLL, if it is already loaded into memory.
|
||||
func (d *LazyDLL) Load() error {
|
||||
// Non-racy version of:
|
||||
// if d.dll != nil {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.dll != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// kernel32.dll is special, since it's where LoadLibraryEx comes from.
|
||||
// The kernel already special-cases its name, so it's always
|
||||
// loaded from system32.
|
||||
var dll *DLL
|
||||
var err error
|
||||
if d.Name == "kernel32.dll" {
|
||||
dll, err = LoadDLL(d.Name)
|
||||
} else {
|
||||
dll, err = loadLibraryEx(d.Name, d.System)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Non-racy version of:
|
||||
// d.dll = dll
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll))
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustLoad is like Load but panics if search fails.
|
||||
func (d *LazyDLL) mustLoad() {
|
||||
e := d.Load()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle returns d's module handle.
|
||||
func (d *LazyDLL) Handle() uintptr {
|
||||
d.mustLoad()
|
||||
return uintptr(d.dll.Handle)
|
||||
}
|
||||
|
||||
// NewProc returns a LazyProc for accessing the named procedure in the DLL d.
|
||||
func (d *LazyDLL) NewProc(name string) *LazyProc {
|
||||
return &LazyProc{l: d, Name: name}
|
||||
}
|
||||
|
||||
// NewLazyDLL creates new LazyDLL associated with DLL file.
|
||||
func NewLazyDLL(name string) *LazyDLL {
|
||||
return &LazyDLL{Name: name}
|
||||
}
|
||||
|
||||
// NewLazySystemDLL is like NewLazyDLL, but will only
|
||||
// search Windows System directory for the DLL if name is
|
||||
// a base name (like "advapi32.dll").
|
||||
func NewLazySystemDLL(name string) *LazyDLL {
|
||||
return &LazyDLL{Name: name, System: true}
|
||||
}
|
||||
|
||||
// A LazyProc implements access to a procedure inside a LazyDLL.
|
||||
// It delays the lookup until the Addr method is called.
|
||||
type LazyProc struct {
|
||||
Name string
|
||||
|
||||
mu sync.Mutex
|
||||
l *LazyDLL
|
||||
proc *Proc
|
||||
}
|
||||
|
||||
// Find searches DLL for procedure named p.Name. It returns
|
||||
// an error if search fails. Find will not search procedure,
|
||||
// if it is already found and loaded into memory.
|
||||
func (p *LazyProc) Find() error {
|
||||
// Non-racy version of:
|
||||
// if p.proc == nil {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.proc == nil {
|
||||
e := p.l.Load()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
proc, e := p.l.dll.FindProc(p.Name)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
// Non-racy version of:
|
||||
// p.proc = proc
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustFind is like Find but panics if search fails.
|
||||
func (p *LazyProc) mustFind() {
|
||||
e := p.Find()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns the address of the procedure represented by p.
|
||||
// The return value can be passed to Syscall to run the procedure.
|
||||
// It will panic if the procedure cannot be found.
|
||||
func (p *LazyProc) Addr() uintptr {
|
||||
p.mustFind()
|
||||
return p.proc.Addr()
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||
// are supplied. It will also panic if the procedure cannot be found.
|
||||
//
|
||||
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||
// Callers must inspect the primary return value to decide whether an error occurred
|
||||
// (according to the semantics of the specific function being called) before consulting
|
||||
// the error. The error will be guaranteed to contain windows.Errno.
|
||||
func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||
p.mustFind()
|
||||
return p.proc.Call(a...)
|
||||
}
|
||||
|
||||
var canDoSearchSystem32Once struct {
|
||||
sync.Once
|
||||
v bool
|
||||
}
|
||||
|
||||
func initCanDoSearchSystem32() {
|
||||
// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
|
||||
// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
|
||||
// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
|
||||
// systems that have KB2533623 installed. To determine whether the
|
||||
// flags are available, use GetProcAddress to get the address of the
|
||||
// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
|
||||
// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
|
||||
// flags can be used with LoadLibraryEx."
|
||||
canDoSearchSystem32Once.v = (modkernel32.NewProc("AddDllDirectory").Find() == nil)
|
||||
}
|
||||
|
||||
func canDoSearchSystem32() bool {
|
||||
canDoSearchSystem32Once.Do(initCanDoSearchSystem32)
|
||||
return canDoSearchSystem32Once.v
|
||||
}
|
||||
|
||||
func isBaseName(name string) bool {
|
||||
for _, c := range name {
|
||||
if c == ':' || c == '/' || c == '\\' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// loadLibraryEx wraps the Windows LoadLibraryEx function.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx
|
||||
//
|
||||
// If name is not an absolute path, LoadLibraryEx searches for the DLL
|
||||
// in a variety of automatic locations unless constrained by flags.
|
||||
// See: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx
|
||||
func loadLibraryEx(name string, system bool) (*DLL, error) {
|
||||
loadDLL := name
|
||||
var flags uintptr
|
||||
if system {
|
||||
if canDoSearchSystem32() {
|
||||
flags = LOAD_LIBRARY_SEARCH_SYSTEM32
|
||||
} else if isBaseName(name) {
|
||||
// WindowsXP or unpatched Windows machine
|
||||
// trying to load "foo.dll" out of the system
|
||||
// folder, but LoadLibraryEx doesn't support
|
||||
// that yet on their system, so emulate it.
|
||||
systemdir, err := GetSystemDirectory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loadDLL = systemdir + "\\" + name
|
||||
}
|
||||
}
|
||||
h, err := LoadLibraryEx(loadDLL, 0, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DLL{Name: name, Handle: h}, nil
|
||||
}
|
||||
|
||||
type errString string
|
||||
|
||||
func (s errString) Error() string { return string(s) }
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.12
|
||||
// +build !go1.12
|
||||
|
||||
// This file is here to allow bodyless functions with go:linkname for Go 1.11
|
||||
// and earlier (see https://golang.org/issue/23311).
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Windows environment variables.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Getenv(key string) (value string, found bool) {
|
||||
return syscall.Getenv(key)
|
||||
}
|
||||
|
||||
func Setenv(key, value string) error {
|
||||
return syscall.Setenv(key, value)
|
||||
}
|
||||
|
||||
func Clearenv() {
|
||||
syscall.Clearenv()
|
||||
}
|
||||
|
||||
func Environ() []string {
|
||||
return syscall.Environ()
|
||||
}
|
||||
|
||||
// Returns a default environment associated with the token, rather than the current
|
||||
// process. If inheritExisting is true, then this environment also inherits the
|
||||
// environment of the current process.
|
||||
func (token Token) Environ(inheritExisting bool) (env []string, err error) {
|
||||
var block *uint16
|
||||
err = CreateEnvironmentBlock(&block, token, inheritExisting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer DestroyEnvironmentBlock(block)
|
||||
blockp := uintptr(unsafe.Pointer(block))
|
||||
for {
|
||||
entry := UTF16PtrToString((*uint16)(unsafe.Pointer(blockp)))
|
||||
if len(entry) == 0 {
|
||||
break
|
||||
}
|
||||
env = append(env, entry)
|
||||
blockp += 2 * (uintptr(len(entry)) + 1)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func Unsetenv(key string) error {
|
||||
return syscall.Unsetenv(key)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
EVENTLOG_SUCCESS = 0
|
||||
EVENTLOG_ERROR_TYPE = 1
|
||||
EVENTLOG_WARNING_TYPE = 2
|
||||
EVENTLOG_INFORMATION_TYPE = 4
|
||||
EVENTLOG_AUDIT_SUCCESS = 8
|
||||
EVENTLOG_AUDIT_FAILURE = 16
|
||||
)
|
||||
|
||||
//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW
|
||||
//sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource
|
||||
//sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Fork, exec, wait, etc.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
errorspkg "errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// EscapeArg rewrites command line argument s as prescribed
|
||||
// in http://msdn.microsoft.com/en-us/library/ms880421.
|
||||
// This function returns "" (2 double quotes) if s is empty.
|
||||
// Alternatively, these transformations are done:
|
||||
// - every back slash (\) is doubled, but only if immediately
|
||||
// followed by double quote (");
|
||||
// - every double quote (") is escaped by back slash (\);
|
||||
// - finally, s is wrapped with double quotes (arg -> "arg"),
|
||||
// but only if there is space or tab inside s.
|
||||
func EscapeArg(s string) string {
|
||||
if len(s) == 0 {
|
||||
return "\"\""
|
||||
}
|
||||
n := len(s)
|
||||
hasSpace := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"', '\\':
|
||||
n++
|
||||
case ' ', '\t':
|
||||
hasSpace = true
|
||||
}
|
||||
}
|
||||
if hasSpace {
|
||||
n += 2
|
||||
}
|
||||
if n == len(s) {
|
||||
return s
|
||||
}
|
||||
|
||||
qs := make([]byte, n)
|
||||
j := 0
|
||||
if hasSpace {
|
||||
qs[j] = '"'
|
||||
j++
|
||||
}
|
||||
slashes := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
default:
|
||||
slashes = 0
|
||||
qs[j] = s[i]
|
||||
case '\\':
|
||||
slashes++
|
||||
qs[j] = s[i]
|
||||
case '"':
|
||||
for ; slashes > 0; slashes-- {
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
}
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
qs[j] = s[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
if hasSpace {
|
||||
for ; slashes > 0; slashes-- {
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
}
|
||||
qs[j] = '"'
|
||||
j++
|
||||
}
|
||||
return string(qs[:j])
|
||||
}
|
||||
|
||||
// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
|
||||
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
|
||||
// or any program that uses CommandLineToArgv.
|
||||
func ComposeCommandLine(args []string) string {
|
||||
var commandLine string
|
||||
for i := range args {
|
||||
if i > 0 {
|
||||
commandLine += " "
|
||||
}
|
||||
commandLine += EscapeArg(args[i])
|
||||
}
|
||||
return commandLine
|
||||
}
|
||||
|
||||
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
|
||||
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
|
||||
// command lines are passed around.
|
||||
func DecomposeCommandLine(commandLine string) ([]string, error) {
|
||||
if len(commandLine) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
var argc int32
|
||||
argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer LocalFree(Handle(unsafe.Pointer(argv)))
|
||||
var args []string
|
||||
for _, v := range (*argv)[:argc] {
|
||||
args = append(args, UTF16ToString((*v)[:]))
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func CloseOnExec(fd Handle) {
|
||||
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
|
||||
}
|
||||
|
||||
// FullPath retrieves the full path of the specified file.
|
||||
func FullPath(name string) (path string, err error) {
|
||||
p, err := UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
n := uint32(100)
|
||||
for {
|
||||
buf := make([]uint16, n)
|
||||
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n <= uint32(len(buf)) {
|
||||
return UTF16ToString(buf[:n]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
|
||||
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
|
||||
var size uintptr
|
||||
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
|
||||
if err != ERROR_INSUFFICIENT_BUFFER {
|
||||
if err == nil {
|
||||
return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
|
||||
al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
|
||||
err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return al, err
|
||||
}
|
||||
|
||||
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
|
||||
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
|
||||
al.pointers = append(al.pointers, value)
|
||||
return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
|
||||
}
|
||||
|
||||
// Delete frees ProcThreadAttributeList's resources.
|
||||
func (al *ProcThreadAttributeListContainer) Delete() {
|
||||
deleteProcThreadAttributeList(al.data)
|
||||
LocalFree(Handle(unsafe.Pointer(al.data)))
|
||||
al.data = nil
|
||||
al.pointers = nil
|
||||
}
|
||||
|
||||
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
|
||||
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
|
||||
return al.data
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
MEM_COMMIT = 0x00001000
|
||||
MEM_RESERVE = 0x00002000
|
||||
MEM_DECOMMIT = 0x00004000
|
||||
MEM_RELEASE = 0x00008000
|
||||
MEM_RESET = 0x00080000
|
||||
MEM_TOP_DOWN = 0x00100000
|
||||
MEM_WRITE_WATCH = 0x00200000
|
||||
MEM_PHYSICAL = 0x00400000
|
||||
MEM_RESET_UNDO = 0x01000000
|
||||
MEM_LARGE_PAGES = 0x20000000
|
||||
|
||||
PAGE_NOACCESS = 0x00000001
|
||||
PAGE_READONLY = 0x00000002
|
||||
PAGE_READWRITE = 0x00000004
|
||||
PAGE_WRITECOPY = 0x00000008
|
||||
PAGE_EXECUTE = 0x00000010
|
||||
PAGE_EXECUTE_READ = 0x00000020
|
||||
PAGE_EXECUTE_READWRITE = 0x00000040
|
||||
PAGE_EXECUTE_WRITECOPY = 0x00000080
|
||||
PAGE_GUARD = 0x00000100
|
||||
PAGE_NOCACHE = 0x00000200
|
||||
PAGE_WRITECOMBINE = 0x00000400
|
||||
PAGE_TARGETS_INVALID = 0x40000000
|
||||
PAGE_TARGETS_NO_UPDATE = 0x40000000
|
||||
|
||||
QUOTA_LIMITS_HARDWS_MIN_DISABLE = 0x00000002
|
||||
QUOTA_LIMITS_HARDWS_MIN_ENABLE = 0x00000001
|
||||
QUOTA_LIMITS_HARDWS_MAX_DISABLE = 0x00000008
|
||||
QUOTA_LIMITS_HARDWS_MAX_ENABLE = 0x00000004
|
||||
)
|
||||
|
||||
type MemoryBasicInformation struct {
|
||||
BaseAddress uintptr
|
||||
AllocationBase uintptr
|
||||
AllocationProtect uint32
|
||||
PartitionId uint16
|
||||
RegionSize uintptr
|
||||
State uint32
|
||||
Protect uint32
|
||||
Type uint32
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
|
||||
winerror="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/winerror.h | sort -Vr | head -n 1)"
|
||||
[[ -n $winerror ]] || { echo "Unable to find winerror.h" >&2; exit 1; }
|
||||
ntstatus="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/ntstatus.h | sort -Vr | head -n 1)"
|
||||
[[ -n $ntstatus ]] || { echo "Unable to find ntstatus.h" >&2; exit 1; }
|
||||
|
||||
declare -A errors
|
||||
|
||||
{
|
||||
echo "// Code generated by 'mkerrors.bash'; DO NOT EDIT."
|
||||
echo
|
||||
echo "package windows"
|
||||
echo "import \"syscall\""
|
||||
echo "const ("
|
||||
|
||||
while read -r line; do
|
||||
unset vtype
|
||||
if [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?([A-Z][A-Z0-9_]+k?)\)? ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?((0x)?[0-9A-Fa-f]+)L?\)? ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
vtype="${BASH_REMATCH[2]}"
|
||||
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +\(\(([A-Z]+)\)((0x)?[0-9A-Fa-f]+)L?\) ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
vtype="${BASH_REMATCH[2]}"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
[[ -n $key && -n $value ]] || continue
|
||||
[[ -z ${errors["$key"]} ]] || continue
|
||||
errors["$key"]="$value"
|
||||
if [[ -v vtype ]]; then
|
||||
if [[ $key == FACILITY_* || $key == NO_ERROR ]]; then
|
||||
vtype=""
|
||||
elif [[ $vtype == *HANDLE* || $vtype == *HRESULT* ]]; then
|
||||
vtype="Handle"
|
||||
else
|
||||
vtype="syscall.Errno"
|
||||
fi
|
||||
last_vtype="$vtype"
|
||||
else
|
||||
vtype=""
|
||||
if [[ $last_vtype == Handle && $value == NO_ERROR ]]; then
|
||||
value="S_OK"
|
||||
elif [[ $last_vtype == syscall.Errno && $value == NO_ERROR ]]; then
|
||||
value="ERROR_SUCCESS"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$key $vtype = $value"
|
||||
done < "$winerror"
|
||||
|
||||
while read -r line; do
|
||||
[[ $line =~ ^#define\ (STATUS_[^\s]+)\ +\(\(NTSTATUS\)((0x)?[0-9a-fA-F]+)L?\) ]] || continue
|
||||
echo "${BASH_REMATCH[1]} NTStatus = ${BASH_REMATCH[2]}"
|
||||
done < "$ntstatus"
|
||||
|
||||
echo ")"
|
||||
} | gofmt > "zerrors_windows.go"
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
|
||||
knownfolders="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/um/KnownFolders.h | sort -Vr | head -n 1)"
|
||||
[[ -n $knownfolders ]] || { echo "Unable to find KnownFolders.h" >&2; exit 1; }
|
||||
|
||||
{
|
||||
echo "// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT."
|
||||
echo
|
||||
echo "package windows"
|
||||
echo "type KNOWNFOLDERID GUID"
|
||||
echo "var ("
|
||||
while read -r line; do
|
||||
[[ $line =~ DEFINE_KNOWN_FOLDER\((FOLDERID_[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+)\) ]] || continue
|
||||
printf "%s = &KNOWNFOLDERID{0x%08x, 0x%04x, 0x%04x, [8]byte{0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x}}\n" \
|
||||
"${BASH_REMATCH[1]}" $(( "${BASH_REMATCH[2]}" )) $(( "${BASH_REMATCH[3]}" )) $(( "${BASH_REMATCH[4]}" )) \
|
||||
$(( "${BASH_REMATCH[5]}" )) $(( "${BASH_REMATCH[6]}" )) $(( "${BASH_REMATCH[7]}" )) $(( "${BASH_REMATCH[8]}" )) \
|
||||
$(( "${BASH_REMATCH[9]}" )) $(( "${BASH_REMATCH[10]}" )) $(( "${BASH_REMATCH[11]}" )) $(( "${BASH_REMATCH[12]}" ))
|
||||
done < "$knownfolders"
|
||||
echo ")"
|
||||
} | gofmt > "zknownfolderids_windows.go"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build generate
|
||||
// +build generate
|
||||
|
||||
package windows
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go setupapi_windows.go
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && race
|
||||
// +build windows,race
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = true
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
runtime.RaceAcquire(addr)
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
runtime.RaceReleaseMerge(addr)
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceReadRange(addr, len)
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceWriteRange(addr, len)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && !race
|
||||
// +build windows,!race
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = false
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
}
|
||||
+1444
File diff suppressed because it is too large
Load Diff
+247
@@ -0,0 +1,247 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
SC_MANAGER_CONNECT = 1
|
||||
SC_MANAGER_CREATE_SERVICE = 2
|
||||
SC_MANAGER_ENUMERATE_SERVICE = 4
|
||||
SC_MANAGER_LOCK = 8
|
||||
SC_MANAGER_QUERY_LOCK_STATUS = 16
|
||||
SC_MANAGER_MODIFY_BOOT_CONFIG = 32
|
||||
SC_MANAGER_ALL_ACCESS = 0xf003f
|
||||
)
|
||||
|
||||
const (
|
||||
SERVICE_KERNEL_DRIVER = 1
|
||||
SERVICE_FILE_SYSTEM_DRIVER = 2
|
||||
SERVICE_ADAPTER = 4
|
||||
SERVICE_RECOGNIZER_DRIVER = 8
|
||||
SERVICE_WIN32_OWN_PROCESS = 16
|
||||
SERVICE_WIN32_SHARE_PROCESS = 32
|
||||
SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS
|
||||
SERVICE_INTERACTIVE_PROCESS = 256
|
||||
SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER
|
||||
SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS
|
||||
|
||||
SERVICE_BOOT_START = 0
|
||||
SERVICE_SYSTEM_START = 1
|
||||
SERVICE_AUTO_START = 2
|
||||
SERVICE_DEMAND_START = 3
|
||||
SERVICE_DISABLED = 4
|
||||
|
||||
SERVICE_ERROR_IGNORE = 0
|
||||
SERVICE_ERROR_NORMAL = 1
|
||||
SERVICE_ERROR_SEVERE = 2
|
||||
SERVICE_ERROR_CRITICAL = 3
|
||||
|
||||
SC_STATUS_PROCESS_INFO = 0
|
||||
|
||||
SC_ACTION_NONE = 0
|
||||
SC_ACTION_RESTART = 1
|
||||
SC_ACTION_REBOOT = 2
|
||||
SC_ACTION_RUN_COMMAND = 3
|
||||
|
||||
SERVICE_STOPPED = 1
|
||||
SERVICE_START_PENDING = 2
|
||||
SERVICE_STOP_PENDING = 3
|
||||
SERVICE_RUNNING = 4
|
||||
SERVICE_CONTINUE_PENDING = 5
|
||||
SERVICE_PAUSE_PENDING = 6
|
||||
SERVICE_PAUSED = 7
|
||||
SERVICE_NO_CHANGE = 0xffffffff
|
||||
|
||||
SERVICE_ACCEPT_STOP = 1
|
||||
SERVICE_ACCEPT_PAUSE_CONTINUE = 2
|
||||
SERVICE_ACCEPT_SHUTDOWN = 4
|
||||
SERVICE_ACCEPT_PARAMCHANGE = 8
|
||||
SERVICE_ACCEPT_NETBINDCHANGE = 16
|
||||
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32
|
||||
SERVICE_ACCEPT_POWEREVENT = 64
|
||||
SERVICE_ACCEPT_SESSIONCHANGE = 128
|
||||
SERVICE_ACCEPT_PRESHUTDOWN = 256
|
||||
|
||||
SERVICE_CONTROL_STOP = 1
|
||||
SERVICE_CONTROL_PAUSE = 2
|
||||
SERVICE_CONTROL_CONTINUE = 3
|
||||
SERVICE_CONTROL_INTERROGATE = 4
|
||||
SERVICE_CONTROL_SHUTDOWN = 5
|
||||
SERVICE_CONTROL_PARAMCHANGE = 6
|
||||
SERVICE_CONTROL_NETBINDADD = 7
|
||||
SERVICE_CONTROL_NETBINDREMOVE = 8
|
||||
SERVICE_CONTROL_NETBINDENABLE = 9
|
||||
SERVICE_CONTROL_NETBINDDISABLE = 10
|
||||
SERVICE_CONTROL_DEVICEEVENT = 11
|
||||
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12
|
||||
SERVICE_CONTROL_POWEREVENT = 13
|
||||
SERVICE_CONTROL_SESSIONCHANGE = 14
|
||||
SERVICE_CONTROL_PRESHUTDOWN = 15
|
||||
|
||||
SERVICE_ACTIVE = 1
|
||||
SERVICE_INACTIVE = 2
|
||||
SERVICE_STATE_ALL = 3
|
||||
|
||||
SERVICE_QUERY_CONFIG = 1
|
||||
SERVICE_CHANGE_CONFIG = 2
|
||||
SERVICE_QUERY_STATUS = 4
|
||||
SERVICE_ENUMERATE_DEPENDENTS = 8
|
||||
SERVICE_START = 16
|
||||
SERVICE_STOP = 32
|
||||
SERVICE_PAUSE_CONTINUE = 64
|
||||
SERVICE_INTERROGATE = 128
|
||||
SERVICE_USER_DEFINED_CONTROL = 256
|
||||
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL
|
||||
|
||||
SERVICE_RUNS_IN_SYSTEM_PROCESS = 1
|
||||
|
||||
SERVICE_CONFIG_DESCRIPTION = 1
|
||||
SERVICE_CONFIG_FAILURE_ACTIONS = 2
|
||||
SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3
|
||||
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4
|
||||
SERVICE_CONFIG_SERVICE_SID_INFO = 5
|
||||
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6
|
||||
SERVICE_CONFIG_PRESHUTDOWN_INFO = 7
|
||||
SERVICE_CONFIG_TRIGGER_INFO = 8
|
||||
SERVICE_CONFIG_PREFERRED_NODE = 9
|
||||
SERVICE_CONFIG_LAUNCH_PROTECTED = 12
|
||||
|
||||
SERVICE_SID_TYPE_NONE = 0
|
||||
SERVICE_SID_TYPE_UNRESTRICTED = 1
|
||||
SERVICE_SID_TYPE_RESTRICTED = 2 | SERVICE_SID_TYPE_UNRESTRICTED
|
||||
|
||||
SC_ENUM_PROCESS_INFO = 0
|
||||
|
||||
SERVICE_NOTIFY_STATUS_CHANGE = 2
|
||||
SERVICE_NOTIFY_STOPPED = 0x00000001
|
||||
SERVICE_NOTIFY_START_PENDING = 0x00000002
|
||||
SERVICE_NOTIFY_STOP_PENDING = 0x00000004
|
||||
SERVICE_NOTIFY_RUNNING = 0x00000008
|
||||
SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010
|
||||
SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020
|
||||
SERVICE_NOTIFY_PAUSED = 0x00000040
|
||||
SERVICE_NOTIFY_CREATED = 0x00000080
|
||||
SERVICE_NOTIFY_DELETED = 0x00000100
|
||||
SERVICE_NOTIFY_DELETE_PENDING = 0x00000200
|
||||
|
||||
SC_EVENT_DATABASE_CHANGE = 0
|
||||
SC_EVENT_PROPERTY_CHANGE = 1
|
||||
SC_EVENT_STATUS_CHANGE = 2
|
||||
|
||||
SERVICE_START_REASON_DEMAND = 0x00000001
|
||||
SERVICE_START_REASON_AUTO = 0x00000002
|
||||
SERVICE_START_REASON_TRIGGER = 0x00000004
|
||||
SERVICE_START_REASON_RESTART_ON_FAILURE = 0x00000008
|
||||
SERVICE_START_REASON_DELAYEDAUTO = 0x00000010
|
||||
|
||||
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
|
||||
)
|
||||
|
||||
type SERVICE_STATUS struct {
|
||||
ServiceType uint32
|
||||
CurrentState uint32
|
||||
ControlsAccepted uint32
|
||||
Win32ExitCode uint32
|
||||
ServiceSpecificExitCode uint32
|
||||
CheckPoint uint32
|
||||
WaitHint uint32
|
||||
}
|
||||
|
||||
type SERVICE_TABLE_ENTRY struct {
|
||||
ServiceName *uint16
|
||||
ServiceProc uintptr
|
||||
}
|
||||
|
||||
type QUERY_SERVICE_CONFIG struct {
|
||||
ServiceType uint32
|
||||
StartType uint32
|
||||
ErrorControl uint32
|
||||
BinaryPathName *uint16
|
||||
LoadOrderGroup *uint16
|
||||
TagId uint32
|
||||
Dependencies *uint16
|
||||
ServiceStartName *uint16
|
||||
DisplayName *uint16
|
||||
}
|
||||
|
||||
type SERVICE_DESCRIPTION struct {
|
||||
Description *uint16
|
||||
}
|
||||
|
||||
type SERVICE_DELAYED_AUTO_START_INFO struct {
|
||||
IsDelayedAutoStartUp uint32
|
||||
}
|
||||
|
||||
type SERVICE_STATUS_PROCESS struct {
|
||||
ServiceType uint32
|
||||
CurrentState uint32
|
||||
ControlsAccepted uint32
|
||||
Win32ExitCode uint32
|
||||
ServiceSpecificExitCode uint32
|
||||
CheckPoint uint32
|
||||
WaitHint uint32
|
||||
ProcessId uint32
|
||||
ServiceFlags uint32
|
||||
}
|
||||
|
||||
type ENUM_SERVICE_STATUS_PROCESS struct {
|
||||
ServiceName *uint16
|
||||
DisplayName *uint16
|
||||
ServiceStatusProcess SERVICE_STATUS_PROCESS
|
||||
}
|
||||
|
||||
type SERVICE_NOTIFY struct {
|
||||
Version uint32
|
||||
NotifyCallback uintptr
|
||||
Context uintptr
|
||||
NotificationStatus uint32
|
||||
ServiceStatus SERVICE_STATUS_PROCESS
|
||||
NotificationTriggered uint32
|
||||
ServiceNames *uint16
|
||||
}
|
||||
|
||||
type SERVICE_FAILURE_ACTIONS struct {
|
||||
ResetPeriod uint32
|
||||
RebootMsg *uint16
|
||||
Command *uint16
|
||||
ActionsCount uint32
|
||||
Actions *SC_ACTION
|
||||
}
|
||||
|
||||
type SC_ACTION struct {
|
||||
Type uint32
|
||||
Delay uint32
|
||||
}
|
||||
|
||||
type QUERY_SERVICE_LOCK_STATUS struct {
|
||||
IsLocked uint32
|
||||
LockOwner *uint16
|
||||
LockDuration uint32
|
||||
}
|
||||
|
||||
//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
|
||||
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
|
||||
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
|
||||
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
|
||||
//sys DeleteService(service Handle) (err error) = advapi32.DeleteService
|
||||
//sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW
|
||||
//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus
|
||||
//sys QueryServiceLockStatus(mgr Handle, lockStatus *QUERY_SERVICE_LOCK_STATUS, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceLockStatusW
|
||||
//sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService
|
||||
//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW
|
||||
//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus
|
||||
//sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW
|
||||
//sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW
|
||||
//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W
|
||||
//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W
|
||||
//sys EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW
|
||||
//sys QueryServiceStatusEx(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceStatusEx
|
||||
//sys NotifyServiceStatusChange(service Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) = advapi32.NotifyServiceStatusChangeW
|
||||
//sys SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) = sechost.SubscribeServiceChangeNotifications?
|
||||
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
|
||||
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
|
||||
//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
|
||||
+1425
File diff suppressed because it is too large
Load Diff
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||
if val < 0 {
|
||||
return "-" + itoa(-val)
|
||||
}
|
||||
var buf [32]byte // big enough for int64
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return string(buf[i:])
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Package windows contains an interface to the low-level operating system
|
||||
// primitives. OS details vary depending on the underlying system, and
|
||||
// by default, godoc will display the OS-specific documentation for the current
|
||||
// system. If you want godoc to display syscall documentation for another
|
||||
// system, set $GOOS and $GOARCH to the desired system. For example, if
|
||||
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
|
||||
// to freebsd and $GOARCH to arm.
|
||||
//
|
||||
// The primary use of this package is inside other packages that provide a more
|
||||
// portable interface to the system, such as "os", "time" and "net". Use
|
||||
// those packages rather than this one if you can.
|
||||
//
|
||||
// For details of the functions and data types in this package consult
|
||||
// the manuals for the appropriate operating system.
|
||||
//
|
||||
// These calls return err == nil to indicate success; otherwise
|
||||
// err represents an operating system error describing the failure and
|
||||
// holds a value of type syscall.Errno.
|
||||
package windows // import "golang.org/x/sys/windows"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/internal/unsafeheader"
|
||||
)
|
||||
|
||||
// ByteSliceFromString returns a NUL-terminated slice of bytes
|
||||
// containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, syscall.EINVAL).
|
||||
func ByteSliceFromString(s string) ([]byte, error) {
|
||||
if strings.IndexByte(s, 0) != -1 {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
a := make([]byte, len(s)+1)
|
||||
copy(a, s)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// BytePtrFromString returns a pointer to a NUL-terminated array of
|
||||
// bytes containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, syscall.EINVAL).
|
||||
func BytePtrFromString(s string) (*byte, error) {
|
||||
a, err := ByteSliceFromString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a[0], nil
|
||||
}
|
||||
|
||||
// ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any
|
||||
// bytes after the NUL removed.
|
||||
func ByteSliceToString(s []byte) string {
|
||||
if i := bytes.IndexByte(s, 0); i != -1 {
|
||||
s = s[:i]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// BytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
|
||||
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated
|
||||
// at a zero byte; if the zero byte is not present, the program may crash.
|
||||
func BytePtrToString(p *byte) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
if *p == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Find NUL terminator.
|
||||
n := 0
|
||||
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||
}
|
||||
|
||||
var s []byte
|
||||
h := (*unsafeheader.Slice)(unsafe.Pointer(&s))
|
||||
h.Data = unsafe.Pointer(p)
|
||||
h.Len = n
|
||||
h.Cap = n
|
||||
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||
// See mksyscall.pl.
|
||||
var _zero uintptr
|
||||
|
||||
func (ts *Timespec) Unix() (sec int64, nsec int64) {
|
||||
return int64(ts.Sec), int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Unix() (sec int64, nsec int64) {
|
||||
return int64(tv.Sec), int64(tv.Usec) * 1000
|
||||
}
|
||||
|
||||
func (ts *Timespec) Nano() int64 {
|
||||
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Nano() int64 {
|
||||
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
|
||||
}
|
||||
+1804
File diff suppressed because it is too large
Load Diff
+3215
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Port uint16
|
||||
Proto *byte
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
_ uint32 // pad to 8 byte boundary
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Proto *byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Port uint16
|
||||
Proto *byte
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
_ uint32 // pad to 8 byte boundary
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Proto *byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
}
|
||||
+9468
File diff suppressed because it is too large
Load Diff
+149
@@ -0,0 +1,149 @@
|
||||
// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT.
|
||||
|
||||
package windows
|
||||
|
||||
type KNOWNFOLDERID GUID
|
||||
|
||||
var (
|
||||
FOLDERID_NetworkFolder = &KNOWNFOLDERID{0xd20beec4, 0x5ca8, 0x4905, [8]byte{0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53}}
|
||||
FOLDERID_ComputerFolder = &KNOWNFOLDERID{0x0ac0837c, 0xbbf8, 0x452a, [8]byte{0x85, 0x0d, 0x79, 0xd0, 0x8e, 0x66, 0x7c, 0xa7}}
|
||||
FOLDERID_InternetFolder = &KNOWNFOLDERID{0x4d9f7874, 0x4e0c, 0x4904, [8]byte{0x96, 0x7b, 0x40, 0xb0, 0xd2, 0x0c, 0x3e, 0x4b}}
|
||||
FOLDERID_ControlPanelFolder = &KNOWNFOLDERID{0x82a74aeb, 0xaeb4, 0x465c, [8]byte{0xa0, 0x14, 0xd0, 0x97, 0xee, 0x34, 0x6d, 0x63}}
|
||||
FOLDERID_PrintersFolder = &KNOWNFOLDERID{0x76fc4e2d, 0xd6ad, 0x4519, [8]byte{0xa6, 0x63, 0x37, 0xbd, 0x56, 0x06, 0x81, 0x85}}
|
||||
FOLDERID_SyncManagerFolder = &KNOWNFOLDERID{0x43668bf8, 0xc14e, 0x49b2, [8]byte{0x97, 0xc9, 0x74, 0x77, 0x84, 0xd7, 0x84, 0xb7}}
|
||||
FOLDERID_SyncSetupFolder = &KNOWNFOLDERID{0x0f214138, 0xb1d3, 0x4a90, [8]byte{0xbb, 0xa9, 0x27, 0xcb, 0xc0, 0xc5, 0x38, 0x9a}}
|
||||
FOLDERID_ConflictFolder = &KNOWNFOLDERID{0x4bfefb45, 0x347d, 0x4006, [8]byte{0xa5, 0xbe, 0xac, 0x0c, 0xb0, 0x56, 0x71, 0x92}}
|
||||
FOLDERID_SyncResultsFolder = &KNOWNFOLDERID{0x289a9a43, 0xbe44, 0x4057, [8]byte{0xa4, 0x1b, 0x58, 0x7a, 0x76, 0xd7, 0xe7, 0xf9}}
|
||||
FOLDERID_RecycleBinFolder = &KNOWNFOLDERID{0xb7534046, 0x3ecb, 0x4c18, [8]byte{0xbe, 0x4e, 0x64, 0xcd, 0x4c, 0xb7, 0xd6, 0xac}}
|
||||
FOLDERID_ConnectionsFolder = &KNOWNFOLDERID{0x6f0cd92b, 0x2e97, 0x45d1, [8]byte{0x88, 0xff, 0xb0, 0xd1, 0x86, 0xb8, 0xde, 0xdd}}
|
||||
FOLDERID_Fonts = &KNOWNFOLDERID{0xfd228cb7, 0xae11, 0x4ae3, [8]byte{0x86, 0x4c, 0x16, 0xf3, 0x91, 0x0a, 0xb8, 0xfe}}
|
||||
FOLDERID_Desktop = &KNOWNFOLDERID{0xb4bfcc3a, 0xdb2c, 0x424c, [8]byte{0xb0, 0x29, 0x7f, 0xe9, 0x9a, 0x87, 0xc6, 0x41}}
|
||||
FOLDERID_Startup = &KNOWNFOLDERID{0xb97d20bb, 0xf46a, 0x4c97, [8]byte{0xba, 0x10, 0x5e, 0x36, 0x08, 0x43, 0x08, 0x54}}
|
||||
FOLDERID_Programs = &KNOWNFOLDERID{0xa77f5d77, 0x2e2b, 0x44c3, [8]byte{0xa6, 0xa2, 0xab, 0xa6, 0x01, 0x05, 0x4a, 0x51}}
|
||||
FOLDERID_StartMenu = &KNOWNFOLDERID{0x625b53c3, 0xab48, 0x4ec1, [8]byte{0xba, 0x1f, 0xa1, 0xef, 0x41, 0x46, 0xfc, 0x19}}
|
||||
FOLDERID_Recent = &KNOWNFOLDERID{0xae50c081, 0xebd2, 0x438a, [8]byte{0x86, 0x55, 0x8a, 0x09, 0x2e, 0x34, 0x98, 0x7a}}
|
||||
FOLDERID_SendTo = &KNOWNFOLDERID{0x8983036c, 0x27c0, 0x404b, [8]byte{0x8f, 0x08, 0x10, 0x2d, 0x10, 0xdc, 0xfd, 0x74}}
|
||||
FOLDERID_Documents = &KNOWNFOLDERID{0xfdd39ad0, 0x238f, 0x46af, [8]byte{0xad, 0xb4, 0x6c, 0x85, 0x48, 0x03, 0x69, 0xc7}}
|
||||
FOLDERID_Favorites = &KNOWNFOLDERID{0x1777f761, 0x68ad, 0x4d8a, [8]byte{0x87, 0xbd, 0x30, 0xb7, 0x59, 0xfa, 0x33, 0xdd}}
|
||||
FOLDERID_NetHood = &KNOWNFOLDERID{0xc5abbf53, 0xe17f, 0x4121, [8]byte{0x89, 0x00, 0x86, 0x62, 0x6f, 0xc2, 0xc9, 0x73}}
|
||||
FOLDERID_PrintHood = &KNOWNFOLDERID{0x9274bd8d, 0xcfd1, 0x41c3, [8]byte{0xb3, 0x5e, 0xb1, 0x3f, 0x55, 0xa7, 0x58, 0xf4}}
|
||||
FOLDERID_Templates = &KNOWNFOLDERID{0xa63293e8, 0x664e, 0x48db, [8]byte{0xa0, 0x79, 0xdf, 0x75, 0x9e, 0x05, 0x09, 0xf7}}
|
||||
FOLDERID_CommonStartup = &KNOWNFOLDERID{0x82a5ea35, 0xd9cd, 0x47c5, [8]byte{0x96, 0x29, 0xe1, 0x5d, 0x2f, 0x71, 0x4e, 0x6e}}
|
||||
FOLDERID_CommonPrograms = &KNOWNFOLDERID{0x0139d44e, 0x6afe, 0x49f2, [8]byte{0x86, 0x90, 0x3d, 0xaf, 0xca, 0xe6, 0xff, 0xb8}}
|
||||
FOLDERID_CommonStartMenu = &KNOWNFOLDERID{0xa4115719, 0xd62e, 0x491d, [8]byte{0xaa, 0x7c, 0xe7, 0x4b, 0x8b, 0xe3, 0xb0, 0x67}}
|
||||
FOLDERID_PublicDesktop = &KNOWNFOLDERID{0xc4aa340d, 0xf20f, 0x4863, [8]byte{0xaf, 0xef, 0xf8, 0x7e, 0xf2, 0xe6, 0xba, 0x25}}
|
||||
FOLDERID_ProgramData = &KNOWNFOLDERID{0x62ab5d82, 0xfdc1, 0x4dc3, [8]byte{0xa9, 0xdd, 0x07, 0x0d, 0x1d, 0x49, 0x5d, 0x97}}
|
||||
FOLDERID_CommonTemplates = &KNOWNFOLDERID{0xb94237e7, 0x57ac, 0x4347, [8]byte{0x91, 0x51, 0xb0, 0x8c, 0x6c, 0x32, 0xd1, 0xf7}}
|
||||
FOLDERID_PublicDocuments = &KNOWNFOLDERID{0xed4824af, 0xdce4, 0x45a8, [8]byte{0x81, 0xe2, 0xfc, 0x79, 0x65, 0x08, 0x36, 0x34}}
|
||||
FOLDERID_RoamingAppData = &KNOWNFOLDERID{0x3eb685db, 0x65f9, 0x4cf6, [8]byte{0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d}}
|
||||
FOLDERID_LocalAppData = &KNOWNFOLDERID{0xf1b32785, 0x6fba, 0x4fcf, [8]byte{0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91}}
|
||||
FOLDERID_LocalAppDataLow = &KNOWNFOLDERID{0xa520a1a4, 0x1780, 0x4ff6, [8]byte{0xbd, 0x18, 0x16, 0x73, 0x43, 0xc5, 0xaf, 0x16}}
|
||||
FOLDERID_InternetCache = &KNOWNFOLDERID{0x352481e8, 0x33be, 0x4251, [8]byte{0xba, 0x85, 0x60, 0x07, 0xca, 0xed, 0xcf, 0x9d}}
|
||||
FOLDERID_Cookies = &KNOWNFOLDERID{0x2b0f765d, 0xc0e9, 0x4171, [8]byte{0x90, 0x8e, 0x08, 0xa6, 0x11, 0xb8, 0x4f, 0xf6}}
|
||||
FOLDERID_History = &KNOWNFOLDERID{0xd9dc8a3b, 0xb784, 0x432e, [8]byte{0xa7, 0x81, 0x5a, 0x11, 0x30, 0xa7, 0x59, 0x63}}
|
||||
FOLDERID_System = &KNOWNFOLDERID{0x1ac14e77, 0x02e7, 0x4e5d, [8]byte{0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7}}
|
||||
FOLDERID_SystemX86 = &KNOWNFOLDERID{0xd65231b0, 0xb2f1, 0x4857, [8]byte{0xa4, 0xce, 0xa8, 0xe7, 0xc6, 0xea, 0x7d, 0x27}}
|
||||
FOLDERID_Windows = &KNOWNFOLDERID{0xf38bf404, 0x1d43, 0x42f2, [8]byte{0x93, 0x05, 0x67, 0xde, 0x0b, 0x28, 0xfc, 0x23}}
|
||||
FOLDERID_Profile = &KNOWNFOLDERID{0x5e6c858f, 0x0e22, 0x4760, [8]byte{0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73}}
|
||||
FOLDERID_Pictures = &KNOWNFOLDERID{0x33e28130, 0x4e1e, 0x4676, [8]byte{0x83, 0x5a, 0x98, 0x39, 0x5c, 0x3b, 0xc3, 0xbb}}
|
||||
FOLDERID_ProgramFilesX86 = &KNOWNFOLDERID{0x7c5a40ef, 0xa0fb, 0x4bfc, [8]byte{0x87, 0x4a, 0xc0, 0xf2, 0xe0, 0xb9, 0xfa, 0x8e}}
|
||||
FOLDERID_ProgramFilesCommonX86 = &KNOWNFOLDERID{0xde974d24, 0xd9c6, 0x4d3e, [8]byte{0xbf, 0x91, 0xf4, 0x45, 0x51, 0x20, 0xb9, 0x17}}
|
||||
FOLDERID_ProgramFilesX64 = &KNOWNFOLDERID{0x6d809377, 0x6af0, 0x444b, [8]byte{0x89, 0x57, 0xa3, 0x77, 0x3f, 0x02, 0x20, 0x0e}}
|
||||
FOLDERID_ProgramFilesCommonX64 = &KNOWNFOLDERID{0x6365d5a7, 0x0f0d, 0x45e5, [8]byte{0x87, 0xf6, 0x0d, 0xa5, 0x6b, 0x6a, 0x4f, 0x7d}}
|
||||
FOLDERID_ProgramFiles = &KNOWNFOLDERID{0x905e63b6, 0xc1bf, 0x494e, [8]byte{0xb2, 0x9c, 0x65, 0xb7, 0x32, 0xd3, 0xd2, 0x1a}}
|
||||
FOLDERID_ProgramFilesCommon = &KNOWNFOLDERID{0xf7f1ed05, 0x9f6d, 0x47a2, [8]byte{0xaa, 0xae, 0x29, 0xd3, 0x17, 0xc6, 0xf0, 0x66}}
|
||||
FOLDERID_UserProgramFiles = &KNOWNFOLDERID{0x5cd7aee2, 0x2219, 0x4a67, [8]byte{0xb8, 0x5d, 0x6c, 0x9c, 0xe1, 0x56, 0x60, 0xcb}}
|
||||
FOLDERID_UserProgramFilesCommon = &KNOWNFOLDERID{0xbcbd3057, 0xca5c, 0x4622, [8]byte{0xb4, 0x2d, 0xbc, 0x56, 0xdb, 0x0a, 0xe5, 0x16}}
|
||||
FOLDERID_AdminTools = &KNOWNFOLDERID{0x724ef170, 0xa42d, 0x4fef, [8]byte{0x9f, 0x26, 0xb6, 0x0e, 0x84, 0x6f, 0xba, 0x4f}}
|
||||
FOLDERID_CommonAdminTools = &KNOWNFOLDERID{0xd0384e7d, 0xbac3, 0x4797, [8]byte{0x8f, 0x14, 0xcb, 0xa2, 0x29, 0xb3, 0x92, 0xb5}}
|
||||
FOLDERID_Music = &KNOWNFOLDERID{0x4bd8d571, 0x6d19, 0x48d3, [8]byte{0xbe, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0e, 0x43}}
|
||||
FOLDERID_Videos = &KNOWNFOLDERID{0x18989b1d, 0x99b5, 0x455b, [8]byte{0x84, 0x1c, 0xab, 0x7c, 0x74, 0xe4, 0xdd, 0xfc}}
|
||||
FOLDERID_Ringtones = &KNOWNFOLDERID{0xc870044b, 0xf49e, 0x4126, [8]byte{0xa9, 0xc3, 0xb5, 0x2a, 0x1f, 0xf4, 0x11, 0xe8}}
|
||||
FOLDERID_PublicPictures = &KNOWNFOLDERID{0xb6ebfb86, 0x6907, 0x413c, [8]byte{0x9a, 0xf7, 0x4f, 0xc2, 0xab, 0xf0, 0x7c, 0xc5}}
|
||||
FOLDERID_PublicMusic = &KNOWNFOLDERID{0x3214fab5, 0x9757, 0x4298, [8]byte{0xbb, 0x61, 0x92, 0xa9, 0xde, 0xaa, 0x44, 0xff}}
|
||||
FOLDERID_PublicVideos = &KNOWNFOLDERID{0x2400183a, 0x6185, 0x49fb, [8]byte{0xa2, 0xd8, 0x4a, 0x39, 0x2a, 0x60, 0x2b, 0xa3}}
|
||||
FOLDERID_PublicRingtones = &KNOWNFOLDERID{0xe555ab60, 0x153b, 0x4d17, [8]byte{0x9f, 0x04, 0xa5, 0xfe, 0x99, 0xfc, 0x15, 0xec}}
|
||||
FOLDERID_ResourceDir = &KNOWNFOLDERID{0x8ad10c31, 0x2adb, 0x4296, [8]byte{0xa8, 0xf7, 0xe4, 0x70, 0x12, 0x32, 0xc9, 0x72}}
|
||||
FOLDERID_LocalizedResourcesDir = &KNOWNFOLDERID{0x2a00375e, 0x224c, 0x49de, [8]byte{0xb8, 0xd1, 0x44, 0x0d, 0xf7, 0xef, 0x3d, 0xdc}}
|
||||
FOLDERID_CommonOEMLinks = &KNOWNFOLDERID{0xc1bae2d0, 0x10df, 0x4334, [8]byte{0xbe, 0xdd, 0x7a, 0xa2, 0x0b, 0x22, 0x7a, 0x9d}}
|
||||
FOLDERID_CDBurning = &KNOWNFOLDERID{0x9e52ab10, 0xf80d, 0x49df, [8]byte{0xac, 0xb8, 0x43, 0x30, 0xf5, 0x68, 0x78, 0x55}}
|
||||
FOLDERID_UserProfiles = &KNOWNFOLDERID{0x0762d272, 0xc50a, 0x4bb0, [8]byte{0xa3, 0x82, 0x69, 0x7d, 0xcd, 0x72, 0x9b, 0x80}}
|
||||
FOLDERID_Playlists = &KNOWNFOLDERID{0xde92c1c7, 0x837f, 0x4f69, [8]byte{0xa3, 0xbb, 0x86, 0xe6, 0x31, 0x20, 0x4a, 0x23}}
|
||||
FOLDERID_SamplePlaylists = &KNOWNFOLDERID{0x15ca69b3, 0x30ee, 0x49c1, [8]byte{0xac, 0xe1, 0x6b, 0x5e, 0xc3, 0x72, 0xaf, 0xb5}}
|
||||
FOLDERID_SampleMusic = &KNOWNFOLDERID{0xb250c668, 0xf57d, 0x4ee1, [8]byte{0xa6, 0x3c, 0x29, 0x0e, 0xe7, 0xd1, 0xaa, 0x1f}}
|
||||
FOLDERID_SamplePictures = &KNOWNFOLDERID{0xc4900540, 0x2379, 0x4c75, [8]byte{0x84, 0x4b, 0x64, 0xe6, 0xfa, 0xf8, 0x71, 0x6b}}
|
||||
FOLDERID_SampleVideos = &KNOWNFOLDERID{0x859ead94, 0x2e85, 0x48ad, [8]byte{0xa7, 0x1a, 0x09, 0x69, 0xcb, 0x56, 0xa6, 0xcd}}
|
||||
FOLDERID_PhotoAlbums = &KNOWNFOLDERID{0x69d2cf90, 0xfc33, 0x4fb7, [8]byte{0x9a, 0x0c, 0xeb, 0xb0, 0xf0, 0xfc, 0xb4, 0x3c}}
|
||||
FOLDERID_Public = &KNOWNFOLDERID{0xdfdf76a2, 0xc82a, 0x4d63, [8]byte{0x90, 0x6a, 0x56, 0x44, 0xac, 0x45, 0x73, 0x85}}
|
||||
FOLDERID_ChangeRemovePrograms = &KNOWNFOLDERID{0xdf7266ac, 0x9274, 0x4867, [8]byte{0x8d, 0x55, 0x3b, 0xd6, 0x61, 0xde, 0x87, 0x2d}}
|
||||
FOLDERID_AppUpdates = &KNOWNFOLDERID{0xa305ce99, 0xf527, 0x492b, [8]byte{0x8b, 0x1a, 0x7e, 0x76, 0xfa, 0x98, 0xd6, 0xe4}}
|
||||
FOLDERID_AddNewPrograms = &KNOWNFOLDERID{0xde61d971, 0x5ebc, 0x4f02, [8]byte{0xa3, 0xa9, 0x6c, 0x82, 0x89, 0x5e, 0x5c, 0x04}}
|
||||
FOLDERID_Downloads = &KNOWNFOLDERID{0x374de290, 0x123f, 0x4565, [8]byte{0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}}
|
||||
FOLDERID_PublicDownloads = &KNOWNFOLDERID{0x3d644c9b, 0x1fb8, 0x4f30, [8]byte{0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0}}
|
||||
FOLDERID_SavedSearches = &KNOWNFOLDERID{0x7d1d3a04, 0xdebb, 0x4115, [8]byte{0x95, 0xcf, 0x2f, 0x29, 0xda, 0x29, 0x20, 0xda}}
|
||||
FOLDERID_QuickLaunch = &KNOWNFOLDERID{0x52a4f021, 0x7b75, 0x48a9, [8]byte{0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f}}
|
||||
FOLDERID_Contacts = &KNOWNFOLDERID{0x56784854, 0xc6cb, 0x462b, [8]byte{0x81, 0x69, 0x88, 0xe3, 0x50, 0xac, 0xb8, 0x82}}
|
||||
FOLDERID_SidebarParts = &KNOWNFOLDERID{0xa75d362e, 0x50fc, 0x4fb7, [8]byte{0xac, 0x2c, 0xa8, 0xbe, 0xaa, 0x31, 0x44, 0x93}}
|
||||
FOLDERID_SidebarDefaultParts = &KNOWNFOLDERID{0x7b396e54, 0x9ec5, 0x4300, [8]byte{0xbe, 0x0a, 0x24, 0x82, 0xeb, 0xae, 0x1a, 0x26}}
|
||||
FOLDERID_PublicGameTasks = &KNOWNFOLDERID{0xdebf2536, 0xe1a8, 0x4c59, [8]byte{0xb6, 0xa2, 0x41, 0x45, 0x86, 0x47, 0x6a, 0xea}}
|
||||
FOLDERID_GameTasks = &KNOWNFOLDERID{0x054fae61, 0x4dd8, 0x4787, [8]byte{0x80, 0xb6, 0x09, 0x02, 0x20, 0xc4, 0xb7, 0x00}}
|
||||
FOLDERID_SavedGames = &KNOWNFOLDERID{0x4c5c32ff, 0xbb9d, 0x43b0, [8]byte{0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4}}
|
||||
FOLDERID_Games = &KNOWNFOLDERID{0xcac52c1a, 0xb53d, 0x4edc, [8]byte{0x92, 0xd7, 0x6b, 0x2e, 0x8a, 0xc1, 0x94, 0x34}}
|
||||
FOLDERID_SEARCH_MAPI = &KNOWNFOLDERID{0x98ec0e18, 0x2098, 0x4d44, [8]byte{0x86, 0x44, 0x66, 0x97, 0x93, 0x15, 0xa2, 0x81}}
|
||||
FOLDERID_SEARCH_CSC = &KNOWNFOLDERID{0xee32e446, 0x31ca, 0x4aba, [8]byte{0x81, 0x4f, 0xa5, 0xeb, 0xd2, 0xfd, 0x6d, 0x5e}}
|
||||
FOLDERID_Links = &KNOWNFOLDERID{0xbfb9d5e0, 0xc6a9, 0x404c, [8]byte{0xb2, 0xb2, 0xae, 0x6d, 0xb6, 0xaf, 0x49, 0x68}}
|
||||
FOLDERID_UsersFiles = &KNOWNFOLDERID{0xf3ce0f7c, 0x4901, 0x4acc, [8]byte{0x86, 0x48, 0xd5, 0xd4, 0x4b, 0x04, 0xef, 0x8f}}
|
||||
FOLDERID_UsersLibraries = &KNOWNFOLDERID{0xa302545d, 0xdeff, 0x464b, [8]byte{0xab, 0xe8, 0x61, 0xc8, 0x64, 0x8d, 0x93, 0x9b}}
|
||||
FOLDERID_SearchHome = &KNOWNFOLDERID{0x190337d1, 0xb8ca, 0x4121, [8]byte{0xa6, 0x39, 0x6d, 0x47, 0x2d, 0x16, 0x97, 0x2a}}
|
||||
FOLDERID_OriginalImages = &KNOWNFOLDERID{0x2c36c0aa, 0x5812, 0x4b87, [8]byte{0xbf, 0xd0, 0x4c, 0xd0, 0xdf, 0xb1, 0x9b, 0x39}}
|
||||
FOLDERID_DocumentsLibrary = &KNOWNFOLDERID{0x7b0db17d, 0x9cd2, 0x4a93, [8]byte{0x97, 0x33, 0x46, 0xcc, 0x89, 0x02, 0x2e, 0x7c}}
|
||||
FOLDERID_MusicLibrary = &KNOWNFOLDERID{0x2112ab0a, 0xc86a, 0x4ffe, [8]byte{0xa3, 0x68, 0x0d, 0xe9, 0x6e, 0x47, 0x01, 0x2e}}
|
||||
FOLDERID_PicturesLibrary = &KNOWNFOLDERID{0xa990ae9f, 0xa03b, 0x4e80, [8]byte{0x94, 0xbc, 0x99, 0x12, 0xd7, 0x50, 0x41, 0x04}}
|
||||
FOLDERID_VideosLibrary = &KNOWNFOLDERID{0x491e922f, 0x5643, 0x4af4, [8]byte{0xa7, 0xeb, 0x4e, 0x7a, 0x13, 0x8d, 0x81, 0x74}}
|
||||
FOLDERID_RecordedTVLibrary = &KNOWNFOLDERID{0x1a6fdba2, 0xf42d, 0x4358, [8]byte{0xa7, 0x98, 0xb7, 0x4d, 0x74, 0x59, 0x26, 0xc5}}
|
||||
FOLDERID_HomeGroup = &KNOWNFOLDERID{0x52528a6b, 0xb9e3, 0x4add, [8]byte{0xb6, 0x0d, 0x58, 0x8c, 0x2d, 0xba, 0x84, 0x2d}}
|
||||
FOLDERID_HomeGroupCurrentUser = &KNOWNFOLDERID{0x9b74b6a3, 0x0dfd, 0x4f11, [8]byte{0x9e, 0x78, 0x5f, 0x78, 0x00, 0xf2, 0xe7, 0x72}}
|
||||
FOLDERID_DeviceMetadataStore = &KNOWNFOLDERID{0x5ce4a5e9, 0xe4eb, 0x479d, [8]byte{0xb8, 0x9f, 0x13, 0x0c, 0x02, 0x88, 0x61, 0x55}}
|
||||
FOLDERID_Libraries = &KNOWNFOLDERID{0x1b3ea5dc, 0xb587, 0x4786, [8]byte{0xb4, 0xef, 0xbd, 0x1d, 0xc3, 0x32, 0xae, 0xae}}
|
||||
FOLDERID_PublicLibraries = &KNOWNFOLDERID{0x48daf80b, 0xe6cf, 0x4f4e, [8]byte{0xb8, 0x00, 0x0e, 0x69, 0xd8, 0x4e, 0xe3, 0x84}}
|
||||
FOLDERID_UserPinned = &KNOWNFOLDERID{0x9e3995ab, 0x1f9c, 0x4f13, [8]byte{0xb8, 0x27, 0x48, 0xb2, 0x4b, 0x6c, 0x71, 0x74}}
|
||||
FOLDERID_ImplicitAppShortcuts = &KNOWNFOLDERID{0xbcb5256f, 0x79f6, 0x4cee, [8]byte{0xb7, 0x25, 0xdc, 0x34, 0xe4, 0x02, 0xfd, 0x46}}
|
||||
FOLDERID_AccountPictures = &KNOWNFOLDERID{0x008ca0b1, 0x55b4, 0x4c56, [8]byte{0xb8, 0xa8, 0x4d, 0xe4, 0xb2, 0x99, 0xd3, 0xbe}}
|
||||
FOLDERID_PublicUserTiles = &KNOWNFOLDERID{0x0482af6c, 0x08f1, 0x4c34, [8]byte{0x8c, 0x90, 0xe1, 0x7e, 0xc9, 0x8b, 0x1e, 0x17}}
|
||||
FOLDERID_AppsFolder = &KNOWNFOLDERID{0x1e87508d, 0x89c2, 0x42f0, [8]byte{0x8a, 0x7e, 0x64, 0x5a, 0x0f, 0x50, 0xca, 0x58}}
|
||||
FOLDERID_StartMenuAllPrograms = &KNOWNFOLDERID{0xf26305ef, 0x6948, 0x40b9, [8]byte{0xb2, 0x55, 0x81, 0x45, 0x3d, 0x09, 0xc7, 0x85}}
|
||||
FOLDERID_CommonStartMenuPlaces = &KNOWNFOLDERID{0xa440879f, 0x87a0, 0x4f7d, [8]byte{0xb7, 0x00, 0x02, 0x07, 0xb9, 0x66, 0x19, 0x4a}}
|
||||
FOLDERID_ApplicationShortcuts = &KNOWNFOLDERID{0xa3918781, 0xe5f2, 0x4890, [8]byte{0xb3, 0xd9, 0xa7, 0xe5, 0x43, 0x32, 0x32, 0x8c}}
|
||||
FOLDERID_RoamingTiles = &KNOWNFOLDERID{0x00bcfc5a, 0xed94, 0x4e48, [8]byte{0x96, 0xa1, 0x3f, 0x62, 0x17, 0xf2, 0x19, 0x90}}
|
||||
FOLDERID_RoamedTileImages = &KNOWNFOLDERID{0xaaa8d5a5, 0xf1d6, 0x4259, [8]byte{0xba, 0xa8, 0x78, 0xe7, 0xef, 0x60, 0x83, 0x5e}}
|
||||
FOLDERID_Screenshots = &KNOWNFOLDERID{0xb7bede81, 0xdf94, 0x4682, [8]byte{0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f}}
|
||||
FOLDERID_CameraRoll = &KNOWNFOLDERID{0xab5fb87b, 0x7ce2, 0x4f83, [8]byte{0x91, 0x5d, 0x55, 0x08, 0x46, 0xc9, 0x53, 0x7b}}
|
||||
FOLDERID_SkyDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||
FOLDERID_OneDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||
FOLDERID_SkyDriveDocuments = &KNOWNFOLDERID{0x24d89e24, 0x2f19, 0x4534, [8]byte{0x9d, 0xde, 0x6a, 0x66, 0x71, 0xfb, 0xb8, 0xfe}}
|
||||
FOLDERID_SkyDrivePictures = &KNOWNFOLDERID{0x339719b5, 0x8c47, 0x4894, [8]byte{0x94, 0xc2, 0xd8, 0xf7, 0x7a, 0xdd, 0x44, 0xa6}}
|
||||
FOLDERID_SkyDriveMusic = &KNOWNFOLDERID{0xc3f2459e, 0x80d6, 0x45dc, [8]byte{0xbf, 0xef, 0x1f, 0x76, 0x9f, 0x2b, 0xe7, 0x30}}
|
||||
FOLDERID_SkyDriveCameraRoll = &KNOWNFOLDERID{0x767e6811, 0x49cb, 0x4273, [8]byte{0x87, 0xc2, 0x20, 0xf3, 0x55, 0xe1, 0x08, 0x5b}}
|
||||
FOLDERID_SearchHistory = &KNOWNFOLDERID{0x0d4c3db6, 0x03a3, 0x462f, [8]byte{0xa0, 0xe6, 0x08, 0x92, 0x4c, 0x41, 0xb5, 0xd4}}
|
||||
FOLDERID_SearchTemplates = &KNOWNFOLDERID{0x7e636bfe, 0xdfa9, 0x4d5e, [8]byte{0xb4, 0x56, 0xd7, 0xb3, 0x98, 0x51, 0xd8, 0xa9}}
|
||||
FOLDERID_CameraRollLibrary = &KNOWNFOLDERID{0x2b20df75, 0x1eda, 0x4039, [8]byte{0x80, 0x97, 0x38, 0x79, 0x82, 0x27, 0xd5, 0xb7}}
|
||||
FOLDERID_SavedPictures = &KNOWNFOLDERID{0x3b193882, 0xd3ad, 0x4eab, [8]byte{0x96, 0x5a, 0x69, 0x82, 0x9d, 0x1f, 0xb5, 0x9f}}
|
||||
FOLDERID_SavedPicturesLibrary = &KNOWNFOLDERID{0xe25b5812, 0xbe88, 0x4bd9, [8]byte{0x94, 0xb0, 0x29, 0x23, 0x34, 0x77, 0xb6, 0xc3}}
|
||||
FOLDERID_RetailDemo = &KNOWNFOLDERID{0x12d4c69e, 0x24ad, 0x4923, [8]byte{0xbe, 0x19, 0x31, 0x32, 0x1c, 0x43, 0xa7, 0x67}}
|
||||
FOLDERID_Device = &KNOWNFOLDERID{0x1c2ac1dc, 0x4358, 0x4b6c, [8]byte{0x97, 0x33, 0xaf, 0x21, 0x15, 0x65, 0x76, 0xf0}}
|
||||
FOLDERID_DevelopmentFiles = &KNOWNFOLDERID{0xdbe8e08e, 0x3053, 0x4bbc, [8]byte{0xb1, 0x83, 0x2a, 0x7b, 0x2b, 0x19, 0x1e, 0x59}}
|
||||
FOLDERID_Objects3D = &KNOWNFOLDERID{0x31c0dd25, 0x9439, 0x4f12, [8]byte{0xbf, 0x41, 0x7f, 0xf4, 0xed, 0xa3, 0x87, 0x22}}
|
||||
FOLDERID_AppCaptures = &KNOWNFOLDERID{0xedc0fe71, 0x98d8, 0x4f4a, [8]byte{0xb9, 0x20, 0xc8, 0xdc, 0x13, 0x3c, 0xb1, 0x65}}
|
||||
FOLDERID_LocalDocuments = &KNOWNFOLDERID{0xf42ee2d3, 0x909f, 0x4907, [8]byte{0x88, 0x71, 0x4c, 0x22, 0xfc, 0x0b, 0xf7, 0x56}}
|
||||
FOLDERID_LocalPictures = &KNOWNFOLDERID{0x0ddd015d, 0xb06c, 0x45d5, [8]byte{0x8c, 0x4c, 0xf5, 0x97, 0x13, 0x85, 0x46, 0x39}}
|
||||
FOLDERID_LocalVideos = &KNOWNFOLDERID{0x35286a68, 0x3c57, 0x41a1, [8]byte{0xbb, 0xb1, 0x0e, 0xae, 0x73, 0xd7, 0x6c, 0x95}}
|
||||
FOLDERID_LocalMusic = &KNOWNFOLDERID{0xa0c69a99, 0x21c8, 0x4671, [8]byte{0x87, 0x03, 0x79, 0x34, 0x16, 0x2f, 0xcf, 0x1d}}
|
||||
FOLDERID_LocalDownloads = &KNOWNFOLDERID{0x7d83ee9b, 0x2244, 0x4e70, [8]byte{0xb1, 0xf5, 0x53, 0x93, 0x04, 0x2a, 0xf1, 0xe4}}
|
||||
FOLDERID_RecordedCalls = &KNOWNFOLDERID{0x2f8b40c2, 0x83ed, 0x48ee, [8]byte{0xb3, 0x83, 0xa1, 0xf1, 0x57, 0xec, 0x6f, 0x9a}}
|
||||
FOLDERID_AllAppMods = &KNOWNFOLDERID{0x7ad67899, 0x66af, 0x43ba, [8]byte{0x91, 0x56, 0x6a, 0xad, 0x42, 0xe6, 0xc5, 0x96}}
|
||||
FOLDERID_CurrentAppMods = &KNOWNFOLDERID{0x3db40b20, 0x2a30, 0x4dbe, [8]byte{0x91, 0x7e, 0x77, 0x1d, 0xd2, 0x1d, 0xd0, 0x99}}
|
||||
FOLDERID_AppDataDesktop = &KNOWNFOLDERID{0xb2c5e279, 0x7add, 0x439f, [8]byte{0xb2, 0x8c, 0xc4, 0x1f, 0xe1, 0xbb, 0xf6, 0x72}}
|
||||
FOLDERID_AppDataDocuments = &KNOWNFOLDERID{0x7be16610, 0x1f7f, 0x44ac, [8]byte{0xbf, 0xf0, 0x83, 0xe1, 0x5f, 0x2f, 0xfc, 0xa1}}
|
||||
FOLDERID_AppDataFavorites = &KNOWNFOLDERID{0x7cfbefbc, 0xde1f, 0x45aa, [8]byte{0xb8, 0x43, 0xa5, 0x42, 0xac, 0x53, 0x6c, 0xc9}}
|
||||
FOLDERID_AppDataProgramData = &KNOWNFOLDERID{0x559d40a3, 0xa036, 0x40fa, [8]byte{0xaf, 0x61, 0x84, 0xcb, 0x43, 0x0a, 0x4d, 0x34}}
|
||||
)
|
||||
+4214
File diff suppressed because it is too large
Load Diff
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
# github.com/danieljoos/wincred v1.1.2
|
||||
## explicit; go 1.13
|
||||
github.com/danieljoos/wincred
|
||||
# golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||
## explicit; go 1.17
|
||||
golang.org/x/sys/execabs
|
||||
golang.org/x/sys/internal/unsafeheader
|
||||
golang.org/x/sys/windows
|
||||
+101
-4
@@ -1,8 +1,13 @@
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
winc "github.com/danieljoos/wincred"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker-credential-helpers/registryurl"
|
||||
)
|
||||
|
||||
// Wincred handles secrets using the Windows credential service.
|
||||
@@ -10,10 +15,13 @@ type Wincred struct{}
|
||||
|
||||
// Add adds new credentials to the windows credentials manager.
|
||||
func (h Wincred) Add(creds *credentials.Credentials) error {
|
||||
credsLabels := []byte(credentials.CredsLabel)
|
||||
g := winc.NewGenericCredential(creds.ServerURL)
|
||||
g.UserName = creds.Username
|
||||
g.CredentialBlob = []byte(creds.Secret)
|
||||
g.Persist = winc.PersistLocalMachine
|
||||
g.Attributes = []winc.CredentialAttribute{{Keyword: "label", Value: credsLabels}}
|
||||
|
||||
return g.Write()
|
||||
}
|
||||
|
||||
@@ -31,14 +39,94 @@ func (h Wincred) Delete(serverURL string) error {
|
||||
|
||||
// Get retrieves credentials from the windows credentials manager.
|
||||
func (h Wincred) Get(serverURL string) (string, string, error) {
|
||||
g, _ := winc.GetGenericCredential(serverURL)
|
||||
target, err := getTarget(serverURL)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
} else if target == "" {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
g, _ := winc.GetGenericCredential(target)
|
||||
if g == nil {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
return g.UserName, string(g.CredentialBlob), nil
|
||||
|
||||
for _, attr := range g.Attributes {
|
||||
if strings.Compare(attr.Keyword, "label") == 0 &&
|
||||
bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 {
|
||||
|
||||
return g.UserName, string(g.CredentialBlob), nil
|
||||
}
|
||||
}
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
// List returns the stored URLs and corresponding usernames.
|
||||
func getTarget(serverURL string) (string, error) {
|
||||
s, err := registryurl.Parse(serverURL)
|
||||
if err != nil {
|
||||
return serverURL, nil
|
||||
}
|
||||
|
||||
creds, err := winc.List()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var targets []string
|
||||
for i := range creds {
|
||||
attrs := creds[i].Attributes
|
||||
for _, attr := range attrs {
|
||||
if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) {
|
||||
targets = append(targets, creds[i].TargetName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if target, found := findMatch(s, targets, exactMatch); found {
|
||||
return target, nil
|
||||
}
|
||||
|
||||
if target, found := findMatch(s, targets, approximateMatch); found {
|
||||
return target, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func findMatch(serverUrl *url.URL, targets []string, matches func(url.URL, url.URL) bool) (string, bool) {
|
||||
for _, target := range targets {
|
||||
tURL, err := registryurl.Parse(target)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if matches(*serverUrl, *tURL) {
|
||||
return target, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func exactMatch(serverURL, target url.URL) bool {
|
||||
return serverURL.String() == target.String()
|
||||
}
|
||||
|
||||
func approximateMatch(serverURL, target url.URL) bool {
|
||||
//if scheme is missing assume it is the same as target
|
||||
if serverURL.Scheme == "" {
|
||||
serverURL.Scheme = target.Scheme
|
||||
}
|
||||
//if port is missing assume it is the same as target
|
||||
if serverURL.Port() == "" && target.Port() != "" {
|
||||
serverURL.Host = serverURL.Host + ":" + target.Port()
|
||||
}
|
||||
//if path is missing assume it is the same as target
|
||||
if serverURL.Path == "" {
|
||||
serverURL.Path = target.Path
|
||||
}
|
||||
return serverURL.String() == target.String()
|
||||
}
|
||||
|
||||
// List returns the stored URLs and corresponding usernames for a given credentials label.
|
||||
func (h Wincred) List() (map[string]string, error) {
|
||||
creds, err := winc.List()
|
||||
if err != nil {
|
||||
@@ -47,7 +135,16 @@ func (h Wincred) List() (map[string]string, error) {
|
||||
|
||||
resp := make(map[string]string)
|
||||
for i := range creds {
|
||||
resp[creds[i].TargetName] = creds[i].UserName
|
||||
attrs := creds[i].Attributes
|
||||
for _, attr := range attrs {
|
||||
if strings.Compare(attr.Keyword, "label") == 0 &&
|
||||
bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 {
|
||||
|
||||
resp[creds[i].TargetName] = creds[i].UserName
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
@@ -19,6 +21,31 @@ func TestWinCredHelper(t *testing.T) {
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
|
||||
// check for and remove remaining credentials from previous fail tests
|
||||
oldauths, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range oldauths {
|
||||
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 {
|
||||
if err := helper.Delete(creds1.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recount for credentials
|
||||
oldauths, err = helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,7 +64,7 @@ func TestWinCredHelper(t *testing.T) {
|
||||
}
|
||||
|
||||
auths, err := helper.List()
|
||||
if err != nil || len(auths) == 0 {
|
||||
if err != nil || len(auths)-len(oldauths) != 1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -60,6 +87,154 @@ func TestWinCredHelper(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperRetrieveAliases verifies that secrets can be accessed
|
||||
// through variations on the URL
|
||||
func TestWinCredHelperRetrieveAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored with port, retrieved without
|
||||
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
|
||||
|
||||
// stored as https, retrieved without scheme
|
||||
{"https://foobar.docker.io", "foobar.docker.io"},
|
||||
|
||||
// stored with path, retrieved without
|
||||
{"https://foobar.docker.io/one/two", "https://foobar.docker.io"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperRetrieveStrict verifies that only matching secrets are
|
||||
// returned.
|
||||
func TestWinCredHelperRetrieveStrict(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored as https, retrieved using http
|
||||
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
|
||||
|
||||
// stored as http, retrieved using https
|
||||
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
|
||||
|
||||
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
|
||||
{"http://foobar.docker.io", "foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports
|
||||
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports TODO is this desired behavior? The other way round does work
|
||||
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching paths
|
||||
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err == nil {
|
||||
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperStoreRetrieve verifies that secrets stored in the
|
||||
// the keychain can be read back using the URL that was used to store them.
|
||||
func TestWinCredHelperStoreRetrieve(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
}{
|
||||
{url: "foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
|
||||
// Note that we don't delete between individual tests here, to verify that
|
||||
// subsequent stores/overwrites don't affect storing / retrieving secrets.
|
||||
for i, te := range tests {
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: te.url,
|
||||
Username: fmt.Sprintf("user-%d", i),
|
||||
Secret: fmt.Sprintf("secret-%d", i),
|
||||
}
|
||||
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
user, secret, err := helper.Get(te.url)
|
||||
if err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
if user != c.Username {
|
||||
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
|
||||
}
|
||||
if secret != c.Secret {
|
||||
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := Wincred{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
|
||||
Reference in New Issue
Block a user