1654668840
easyssh-proxy
easyssh-proxy provides a simple implementation of some SSH protocol features in Go.
This project is forked from easyssh but add some features as the following.
+--------+ +----------+ +-----------+
| Laptop | <--> | Jumphost | <--> | FooServer |
+--------+ +----------+ +-----------+
OR
+--------+ +----------+ +-----------+
| Laptop | <--> | Firewall | <--> | FooServer |
+--------+ +----------+ +-----------+
192.168.1.5 121.1.2.3 10.10.29.68
You can see ssh
, scp
, ProxyCommand
on examples
folder.
package main
import (
"fmt"
"time"
"github.com/appleboy/easyssh-proxy"
)
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
User: "appleboy",
Server: "example.com",
// Optional key or Password without either we try to contact your agent SOCKET
// Password: "password",
// Paste your source content of private key
// Key: `-----BEGIN RSA PRIVATE KEY-----
// MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
// 7XC9wlRna4b3Ln8ew3q1ZcBjXwD4ppbTlmwAfQIaZTGJUgQbdsO9YA==
// -----END RSA PRIVATE KEY-----
// `,
KeyPath: "/Users/username/.ssh/id_rsa",
Port: "22",
Timeout: 60 * time.Second,
// Parse PrivateKey With Passphrase
Passphrase: "1234",
// Optional fingerprint SHA256 verification
// Get Fingerprint: ssh.FingerprintSHA256(key)
// Fingerprint: "SHA256:mVPwvezndPv/ARoIadVY98vAC0g+P/5633yTC4d/wXE"
// Enable the use of insecure ciphers and key exchange methods.
// This enables the use of the the following insecure ciphers and key exchange methods:
// - aes128-cbc
// - aes192-cbc
// - aes256-cbc
// - 3des-cbc
// - diffie-hellman-group-exchange-sha256
// - diffie-hellman-group-exchange-sha1
// Those algorithms are insecure and may allow plaintext data to be recovered by an attacker.
// UseInsecureCipher: true,
}
// Call Run method with command you want to run on remote server.
stdout, stderr, done, err := ssh.Run("ls -al", 60*time.Second)
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
fmt.Println("don is :", done, "stdout is :", stdout, "; stderr is :", stderr)
}
}
package main
import (
"fmt"
"github.com/appleboy/easyssh-proxy"
)
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
User: "appleboy",
Server: "example.com",
Password: "123qwe",
Port: "22",
}
// Call Scp method with file you want to upload to remote server.
// Please make sure the `tmp` floder exists.
err := ssh.Scp("/root/source.csv", "/tmp/target.csv")
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
fmt.Println("success")
}
}
ssh := &easyssh.MakeConfig{
User: "drone-scp",
Server: "localhost",
Port: "22",
KeyPath: "./tests/.ssh/id_rsa",
Proxy: easyssh.DefaultConfig{
User: "drone-scp",
Server: "localhost",
Port: "22",
KeyPath: "./tests/.ssh/id_rsa",
},
}
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
Server: "localhost",
User: "drone-scp",
KeyPath: "./tests/.ssh/id_rsa",
Port: "22",
Timeout: 60 * time.Second,
}
// Call Run method with command you want to run on remote server.
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream("for i in {1..5}; do echo ${i}; sleep 1; done; exit 2;", 60*time.Second)
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
// read from the output channel until the done signal is passed
isTimeout := true
loop:
for {
select {
case isTimeout = <-doneChan:
break loop
case outline := <-stdoutChan:
fmt.Println("out:", outline)
case errline := <-stderrChan:
fmt.Println("err:", errline)
case err = <-errChan:
}
}
// get exit code or command error.
if err != nil {
fmt.Println("err: " + err.Error())
}
// command time out
if !isTimeout {
fmt.Println("Error: command timeout")
}
}
}
Author: Appleboy
Source Code: https://github.com/appleboy/easyssh-proxy
License: MIT license
1599854400
Go announced Go 1.15 version on 11 Aug 2020. Highlighted updates and features include Substantial improvements to the Go linker, Improved allocation for small objects at high core counts, X.509 CommonName deprecation, GOPROXY supports skipping proxies that return errors, New embedded tzdata package, Several Core Library improvements and more.
As Go promise for maintaining backward compatibility. After upgrading to the latest Go 1.15 version, almost all existing Golang applications or programs continue to compile and run as older Golang version.
#go #golang #go 1.15 #go features #go improvement #go package #go new features
1654668840
easyssh-proxy
easyssh-proxy provides a simple implementation of some SSH protocol features in Go.
This project is forked from easyssh but add some features as the following.
+--------+ +----------+ +-----------+
| Laptop | <--> | Jumphost | <--> | FooServer |
+--------+ +----------+ +-----------+
OR
+--------+ +----------+ +-----------+
| Laptop | <--> | Firewall | <--> | FooServer |
+--------+ +----------+ +-----------+
192.168.1.5 121.1.2.3 10.10.29.68
You can see ssh
, scp
, ProxyCommand
on examples
folder.
package main
import (
"fmt"
"time"
"github.com/appleboy/easyssh-proxy"
)
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
User: "appleboy",
Server: "example.com",
// Optional key or Password without either we try to contact your agent SOCKET
// Password: "password",
// Paste your source content of private key
// Key: `-----BEGIN RSA PRIVATE KEY-----
// MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
// 7XC9wlRna4b3Ln8ew3q1ZcBjXwD4ppbTlmwAfQIaZTGJUgQbdsO9YA==
// -----END RSA PRIVATE KEY-----
// `,
KeyPath: "/Users/username/.ssh/id_rsa",
Port: "22",
Timeout: 60 * time.Second,
// Parse PrivateKey With Passphrase
Passphrase: "1234",
// Optional fingerprint SHA256 verification
// Get Fingerprint: ssh.FingerprintSHA256(key)
// Fingerprint: "SHA256:mVPwvezndPv/ARoIadVY98vAC0g+P/5633yTC4d/wXE"
// Enable the use of insecure ciphers and key exchange methods.
// This enables the use of the the following insecure ciphers and key exchange methods:
// - aes128-cbc
// - aes192-cbc
// - aes256-cbc
// - 3des-cbc
// - diffie-hellman-group-exchange-sha256
// - diffie-hellman-group-exchange-sha1
// Those algorithms are insecure and may allow plaintext data to be recovered by an attacker.
// UseInsecureCipher: true,
}
// Call Run method with command you want to run on remote server.
stdout, stderr, done, err := ssh.Run("ls -al", 60*time.Second)
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
fmt.Println("don is :", done, "stdout is :", stdout, "; stderr is :", stderr)
}
}
package main
import (
"fmt"
"github.com/appleboy/easyssh-proxy"
)
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
User: "appleboy",
Server: "example.com",
Password: "123qwe",
Port: "22",
}
// Call Scp method with file you want to upload to remote server.
// Please make sure the `tmp` floder exists.
err := ssh.Scp("/root/source.csv", "/tmp/target.csv")
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
fmt.Println("success")
}
}
ssh := &easyssh.MakeConfig{
User: "drone-scp",
Server: "localhost",
Port: "22",
KeyPath: "./tests/.ssh/id_rsa",
Proxy: easyssh.DefaultConfig{
User: "drone-scp",
Server: "localhost",
Port: "22",
KeyPath: "./tests/.ssh/id_rsa",
},
}
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
Server: "localhost",
User: "drone-scp",
KeyPath: "./tests/.ssh/id_rsa",
Port: "22",
Timeout: 60 * time.Second,
}
// Call Run method with command you want to run on remote server.
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream("for i in {1..5}; do echo ${i}; sleep 1; done; exit 2;", 60*time.Second)
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
// read from the output channel until the done signal is passed
isTimeout := true
loop:
for {
select {
case isTimeout = <-doneChan:
break loop
case outline := <-stdoutChan:
fmt.Println("out:", outline)
case errline := <-stderrChan:
fmt.Println("err:", errline)
case err = <-errChan:
}
}
// get exit code or command error.
if err != nil {
fmt.Println("err: " + err.Error())
}
// command time out
if !isTimeout {
fmt.Println("Error: command timeout")
}
}
}
Author: Appleboy
Source Code: https://github.com/appleboy/easyssh-proxy
License: MIT license
1653473460
Rospo is a tool meant to create secure and reliable SSH tunnels. A single binary includes both client and server. It's meant to make SSH tunnels fun and understendable again
crypto/ssh
package )human readable
yaml config fileRospo actually full supports *nix oses and Windows 10+
Install rospo using Homebrew
brew install rospo
Platform | Architecture | URL |
---|---|---|
Microsoft Windows | amd64 | https://github.com/ferama/rospo/releases/latest/download/rospo-windows-amd64.exe |
You can use the docker ditribution where useful/needed. Look at an example on kubernetes here ./hack/k8s
docker pull ghcr.io/ferama/rospo
docker run ghcr.io/ferama/rospo rospo help
Rospo supports keys based auth and password auth. Keys based one is always the preferred, so it is better if identity, authorized_keys etc are always correctly setup.
Usage example:
Starts an embedded ssh server and reverse proxy the port (2222 by default) to remote_server
$ rospo revshell user@server:port
Forwards the local 5000 port to the remote 6000 on the remote_server
$ rospo tun forward -l :5000 -r :6000 user@server:port
Get more detailed help on each command runnig
$ rospo tun forward --help
$ rospo tun reverse --help
$ rospo sshd --help
For more complex use cases and more options, you can use a config file
$ rospo config.yaml
Look at the config_template.yaml for all the available options.
Rospo supports a cool ui too. The ui will let you handle tunnels configuration at runtime through the web interface. You can start/stop new tunnels at runtime.
Tunnels that are configured through the rospo config file will not be administrable from the ui.
Why use an embedded sshd server you might ask me. Suppose you have a Windows WSL instance that you want to access remotely without complicated setups on firewalls and other hassles and annoyances. With rospo you can do it in ONE simple step:
$ rospo revshell remote_ssh_server
This command will run an embedded sshd server on your wsl instance and reverse proxy its port to the remote_ssh_server
The only assumption here is that you have access to remote_ssh_server
. The command will open a socket (on port 2222 by default) into remote_ssh_server
that you can use to log back to WSL using a standard ssh client with a command like:
$ ssh -p 2222 localhost
Or even better (why not!) with rospo you can reverse proxy a powershell. Using rospo for windows:
rospo.exe revshell remote_ssh_server
Rospo support execution as a service on windows. This means that you can create a persistent tunnel that can be installed as a service and started automatically with the machine.
Let's do this with the Windows Remote Desktop service.
Create a rospo conf file like this:
sshclient:
server: your-rospo-or-sshd-server-uri:2222
identity: "c:\\absolute_path_to_your\\id_rsa"
known_hosts: "C:\\absolute_path_to_your\\known_hosts"
tunnel:
- remote: :3389
local: :3389 # the windows remote desktop port
forward: false
Launch a terminal (powershell) with Administrative rights. You can then perform the following actions:
# create the rospo service
sc.exe create rospo start= auto DisplayName= Rospo binpath= "C:\rospo.exe C:\conf.yaml"
# start service
sc.exe start rospo
# query service status
sc.exe query rospo
# stop and delete the service
sc.exe stop rospo; sc.exe delete rospo
Rospo supports multiple tunnels on the same ssh connetion. To exploit the full power of rospo for more complex cases, you should/need to use a scenario config file. Let's define one. Create a file named config.yaml
with the following contents
sshclient:
server: myuser@remote_server_address
identity: "~/.ssh/id_rsa"
jump_hosts:
- uri: anotheruser@jumphost_address
identity: "~/.ssh/id_rsa"
tunnel:
- remote: ":8000"
local: ":8000"
forward: yes
- remote: ":9999"
local: ":9999"
forward: yes
- remote: ":5000"
local: ":5000"
forward: no
Launch rospo using the config file instead of the cli parameters:
$ rospo config.yaml
What's happens here is that rospo will connect to remote_server_address
through the jumphost_address
server and will:
remote_server_address
machineremote_server_address
machineBut these are just an examples. Rospo can do a lot more.
Tunnels are fully secured using standard ssh mechanisms. Rospo will generate server identity file on first run and uses standard authorized_keys
and user known_hosts
files.
Rospo tunnel are monitored and keeped up in the event of network issues.
Many times during development on k8s you need to port-forward some of the pods services for local development and/or tests. You need the port forward maybe because that services are not meant to be exposed through the internet or for whatever reason.
Rospo can come to the rescue here. You can create a rospo.conf
like this:
sshclient:
identity: "/etc/rospo/id_rsa"
server: my-rospo-or-standard-sshd-server:2222
known_hosts: "/etc/rospo/known_hosts"
tunnel:
- remote: "0.0.0.0:9200"
local: "elasticsearch-master.mynamespace:9200"
forward: no
- remote: "0.0.0.0:8080"
local: "demo-app.mynamespace:8080"
forward: no
You need to create the keys accordingly and put them correctly on the target server. After that you can run a kubernetes pod that keeps up the tunnels and let you securely access the services from a machine inside your local network. Please refer to the example in ./hack/k8s for more details.
In this scenario the k8s pods act as a bridge between kubernetes services and the reverse tunnels.
You are going to reverse forward the pod local reachable services ports to the desired host (my-rospo-or-standard-sshd-server:2222 in the example above)
Author: Ferama
Source Code: https://github.com/ferama/rospo
License: MIT license
1648891380
Simple Features
Simple Features is a 2D geometry library that provides Go types that model geometries, as well as algorithms that operate on them.
It's a pure Go Implementation of the OpenGIS Consortium's Simple Feature Access Specification (which can be found here). This is the same specification that GEOS, JTS, and PostGIS implement, so the Simple Features API will be familiar to developers who have used those libraries before.
Type | Example | Description |
---|---|---|
Point | Point is a single location in space. | |
MultiPoint | MultiPoint is collection of points in space. | |
LineString | LineString is curve defined by linear interpolation between a set of control points. | |
MultiLineString | MultiLineString is a collection of LineStrings. | |
Polygon | Polygon is a planar surface geometry that bounds some area. It may have holes. | |
MultiPolygon | Polygon is collection of Polygons (with some constraints on how the Polygons interact with each other). | |
GeometryCollection | GeometryCollection is an unconstrained collection of geometries. | |
Geometry | Geometry holds any type of geometry (Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, or GeometryCollection). It's the type that the Simple Features library uses when it needs to represent geometries in a generic way. | |
Envelope | Envelope is an axis aligned bounding box typically used to describe the spatial extent of other geometric entities. |
Simple features supports the following external geometry representation formats:
Format | Example | Description |
---|---|---|
WKT | POLYGON((0 0,0 1,1 1,1 0,0 0)) | Well Known Text is a human readable format for storing geometries. It's often the lowest common denominator geometry format, and is useful for integration with other GIS applications. |
WKB | <binary> | Well Known Binary is a machine readable format that is efficient for computers to use (both from a processing and storage space perspective). WKB is a good choice for transferring geometries to and from PostGIS and other databases that support geometric types. |
GeoJSON | {"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]} | GeoJSON represents geometries in a similar way to WKB, but is based on the JSON format. This makes it ideal to use with web APIs or other situations where JSON would normally be used. |
TWKB | <binary> | Tiny Well Known Binary is a multi-purpose compressed binary format for serialising vector geometries into a stream of bytes. It emphasises minimising the size of the serialised representation. It's a good choice when space is at a premium (e.g. for storage within a web token). |
The following algorithms are supported:
Miscellaneous Algorithms | Description |
---|---|
Area | Finds the area of the geometry (for Polygons and MultiPolygons). |
Centroid | Finds the centroid of the geometry. |
ConvexHull | Finds the convex hull of the geometry. |
Distance | Finds the shortest distance between two geometries. |
Envelope | Finds the smallest axis-aligned bounding-box that surrounds the geometry. |
ExactEquals | Determines if two geometries are structurally equal. |
Length | Finds the length of the geometry (for LineStrings and MultiLineStrings). |
PointOnSurface | Finds a point that lies inside the geometry. |
Relate | Calculates the DE-9IM intersection describing the relationship between two geometries. |
Simplify | Simplifies a geometry using the Ramer–Douglas–Peucker algorithm. |
Set Operations | Description |
---|---|
Union | Joins the parts from two geometries together. |
Intersection | Finds the parts of two geometries that are in common. |
Difference | Finds the parts of a geometry that are not also part of another geometry. |
SymmetricDifference | Finds the parts of two geometries that are not in common. |
Named Spatial Predicates | Description |
---|---|
Equals | Determines if two geometries are topologically equal. |
Intersects | Determines if two geometries intersect with each other. |
Disjoint | Determines if two geometries have no common points. |
Contains | Determines if one geometry contains another. |
CoveredBy | Determines if one geometry is covered by another. |
Covers | Determines if one geometry covers another. |
Overlaps | Determines if one geometry overlaps another. |
Touches | Determines if one geometry touches another. |
Within | Determines if one geometry is within another. |
Crosses | Determines if one geometry crosses another. |
A GEOS CGO wrapper is also provided, giving access to functionality not yet implemented natively in Go. The wrapper is implemented in a separate package, meaning that library users who don't need this additional functionality don't need to expose themselves to CGO.
The following examples show some common operations (errors are omitted for brevity).
Encoding and decoding WKT:
// Unmarshal from WKT
input := "POLYGON((0 0,0 1,1 1,1 0,0 0))"
g, _ := geom.UnmarshalWKT(input)
// Marshal to WKT
output := g.AsText()
fmt.Println(output) // Prints: POLYGON((0 0,0 1,1 1,1 0,0 0))
Encoding and decoding WKB directly:
// Marshal as WKB
coords := geom.Coordinates{XY: geom.XY{1.5, 2.5}}
pt := geom.NewPoint(coords)
wkb := pt.AsBinary()
fmt.Println(wkb) // Prints: [1 1 0 0 0 0 0 0 0 0 0 248 63 0 0 0 0 0 0 4 64]
// Unmarshal from WKB
fromWKB, _ := geom.UnmarshalWKB(wkb)
fmt.Println(fromWKB.AsText()) // POINT(1.5 2.5)
Encoding and decoding WKB for integration with PostGIS:
db, _ := sql.Open("postgres", "postgres://...")
db.Exec(`
CREATE TABLE my_table (
my_geom geometry(geometry, 4326),
population double precision
)`,
)
// Insert our geometry and population data into PostGIS via WKB.
coords := geom.Coordinates{XY: geom.XY{-74.0, 40.7}}
nyc := geom.NewPoint(coords)
db.Exec(`
INSERT INTO my_table
(my_geom, population)
VALUES (ST_GeomFromWKB($1, 4326), $2)`,
nyc, 8.4e6,
)
// Get the geometry and population data back out of PostGIS via WKB.
var location geom.Geometry
var population float64
db.QueryRow(`
SELECT ST_AsBinary(my_geom), population
FROM my_table LIMIT 1`,
).Scan(&location, &population)
fmt.Println(location.AsText(), population) // Prints: POINT(-74 40.7) 8.4e+06
Encoding and decoding GeoJSON directly:
// Unmarshal geometry from GeoJSON.
raw := `{"type":"Point","coordinates":[-74.0,40.7]}`
var g geom.Geometry
json.NewDecoder(strings.NewReader(raw)).Decode(&g)
fmt.Println(g.AsText()) // Prints: POINT(-74 40.7)
// Marshal back to GeoJSON.
enc := json.NewEncoder(os.Stdout)
enc.Encode(g) // Prints: {"type":"Point","coordinates":[-74,40.7]}
Geometries can also be part of larger structs:
type CityPopulation struct {
Location geom.Geometry `json:"loc"`
Population int `json:"pop"`
}
// Unmarshal geometry from GeoJSON.
raw := `{"loc":{"type":"Point","coordinates":[-74.0,40.7]},"pop":8400000}`
var v CityPopulation
json.NewDecoder(strings.NewReader(raw)).Decode(&v)
fmt.Println(v.Location.AsText()) // Prints: POINT(-74 40.7)
fmt.Println(v.Population) // Prints: 8400000
// Marshal back to GeoJSON.
enc := json.NewEncoder(os.Stdout)
enc.Encode(v) // Prints: {"loc":{"type":"Point","coordinates":[-74,40.7]},"pop":8400000}
Author: Peterstace
Source Code: https://github.com/peterstace/simplefeatures
License: MIT License
1654831620
SKM is a simple and powerful SSH Keys Manager. It helps you to manage your multiple SSH keys easily!
brew tap timothyye/tap
brew install timothyye/tap/skm
go get github.com/TimothyYe/skm/cmd/skm
Download it from releases and extact it to /usr/bin or your PATH directory.
% skm
SKM V0.8.5
https://github.com/TimothyYe/skm
NAME:
SKM - Manage your multiple SSH keys easily
USAGE:
skm [global options] command [command options] [arguments...]
VERSION:
0.8.5
COMMANDS:
init, i Initialize SSH keys store for the first time usage.
create, c Create a new SSH key.
ls, l List all the available SSH keys.
use, u Set specific SSH key as default by its alias name.
delete, d Delete specific SSH key by alias name.
rename, rn Rename SSH key alias name to a new one.
copy, cp Copy current SSH public key to a remote host.
display, dp Display the current SSH public key or specific SSH public key by alias name.
backup, b Backup all SSH keys to an archive file.
restore, r Restore SSH keys from an existing archive file.
cache Add your SSH to SSH agent cache via alias name.
help, h Shows a list of commands or help for one command.
GLOBAL OPTIONS:
--store-path value Path where SKM should store its profiles (default: "/Users/timothy/.skm")
--ssh-path value Path to a .ssh folder (default: "/Users/timothy/.ssh")
--restic-path value Path to the restic binary
--help, -h show help
--version, -v print the version
You should initialize the SSH key store for the first time use:
% skm init
✔ SSH key store initialized!
So, where are my SSH keys? SKM will create SSH key store at $HOME/.skm
and put all the SSH keys in it.
NOTE: If you already have id_rsa & id_rsa.pub key pairs in $HOME/.ssh
, SKM will move them to $HOME/.skm/default
NOTE: Currently ONLY RSA and ED25519 keys are supported!
skm create prod -C "abc@abc.com"
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/timothy/.skm/prod/id_rsa.
Your public key has been saved in /Users/timothy/.skm/prod/id_rsa.pub.
...
✔ SSH key [prod] created!
% skm ls
✔ Found 3 SSH key(s)!
-> default
dev
prod
% skm use dev
Now using SSH key: dev
You can just type skm use
, then a prompt UI will help you to choose the right SSH key:
% skm display
Or display specific SSH public key by alias name:
% skm display prod
% skm delete prod
Please confirm to delete SSH key [prod] [y/n]: y
✔ SSH key [prod] deleted!
% skm cp timothy@example.com
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/timothy/.skm/default/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
timothy@example.com's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'timothy@example.com'"
and check to make sure that only the key(s) you wanted were added.
✔ Current SSH key already copied to remote host
% skm rn test tmp
✔ SSH key [test] renamed to [tmp]
Backup all your SSH keys to $HOME directory by default.
% skm backup
a .
a ./test
a ./default
a ./dev
a ./dev/id_rsa
a ./dev/id_rsa.pub
a ./default/id_rsa
a ./default/id_rsa.pub
a ./test/id_rsa
a ./test/id_rsa.pub
✔ All SSH keys backup to: /Users/timothy/skm-20171016170707.tar
If you have restic installed then you can also use that to create backups of your SKM store:
# First, you need a password for your repository
% if [[ ! -f ~/.skm-backups.passwd ]]; then
% openssl rand -hex 64 > ~/.skm-backups.passwd
% fi
% skm backup --restic
repository ... opened successfully, password is correct
Files: 0 new, 1 changed, 4 unmodified
Dirs: 0 new, 0 changed, 0 unmodified
Added to the repo: 1.179 KiB
processed 5 files, 2.593 KiB in 0:00
snapshot $SNAPSHOT saved
✔ Backup to /Users/$USER/.skm-backups complete
% skm restore ~/skm-20171016172828.tar.gz
x ./
x ./test/
x ./default/
x ./dev/
x ./dev/id_rsa
x ./dev/id_rsa.pub
x ./default/._id_rsa
x ./default/id_rsa
x ./default/._id_rsa.pub
x ./default/id_rsa.pub
x ./test/id_rsa
x ./test/id_rsa.pub
✔ All SSH keys restored to /Users/timothy/.skm
Again, SKM also supports restic to create and restore backups:
% skm restore --restic --restic-snapshot $SNAPSHOT
repository $REPO opened successfully, password is correct
restoring <Snapshot $SNAPSHOT of [/Users/$USER/.skm] at 2018-10-03 19:40:33.333130348 +0200 CEST by $USER@$HOST> to /Users/$USER/.skm
✔ Backup restored to /Users/$USER/.skm
You can use cache
command to cache your SSH key into SSH agent's cache via SSH alias name.
Cache your SSH key
λ tim [~/]
→ skm cache --add my
Enter passphrase for /Users/timothy/.skm/my/id_rsa:
Identity added: /Users/timothy/.skm/my/id_rsa (/Users/timothy/.skm/my/id_rsa)
✔ SSH key [my] already added into cache
Remove your SSH key from cache
λ tim [~/]
→ ./skm cache --del my
Identity removed: /Users/timothy/.skm/my/id_rsa (MyKEY)
✔ SSH key [my] removed from cache
List your cached SSH keys from SSH agent
λ tim [~/]
→ ./skm cache --list
2048 SHA256:qAVcwc0tdUOCjH3sTskwxAmfMQiL2sKtfPBXFnUoZHQ /Users/timothy/.skm/my/id_rsa (RSA)
By default, SKM uses $HOME/.skm
as the default path of SSH key store. You can define your customized key store path in your ~/.bashrc
or ~/.zshrc
by adding:
SKM_STORE_PATH=/usr/local/.skm
Edit and place a executable file named hook
at the specified key directory, for example:
~/.skm/prod/hook
This hook file can be both an executable binary file or an executable script file.
SKM will call this hook file after switching default SSH key to it, you can do some stuff in this hook file.
For example, if you want to use different git username & email after you switch to use a different SSH key, you can create one hook file, and put shell commands in it:
#!/bin/bash
git config --global user.name "YourNewName"
git config --global user.email "YourNewEmail@example.com"
Then make this hook file executable:
chmod +x hook
SKM will call this hook file and change git global settings for you!
Author: TimothyYe
Source Code: https://github.com/TimothyYe/skm
License: MIT license