Golang SSH tunneling and ProxyJump - linux

What I need is to perform the equivalent of the following command but in Go code:
ssh -L 9999:192.168.1.1:80 -J root#[IPv6 address] myuser#100.1.1.100
I'm not even sure where to start with this one.
I haven't been able to find any examples online and I'm at a loss.
Does anyone know how this could be done in Go?

package main
import (
"io"
"log"
"net"
"golang.org/x/crypto/ssh"
)
func main() {
client, err := ssh.Dial("tcp", "100.1.1.100:22", &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{ssh.Password("")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
log.Panicln(err)
return
}
log.Println("init ssh client")
ln, err := net.Listen("tcp", ":9999")
if err != nil {
log.Panicln(err)
return
}
log.Println("local listen")
for {
localconn, err := ln.Accept()
if err != nil {
log.Panicln(err)
return
}
sshconn, err := client.DialTCP("", nil, &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 80})
if err != nil {
log.Panicln(err)
return
}
// local <--> remote
go func() {
errc := make(chan error, 1)
spc := switchProtocolCopier{user: localconn, backend: sshconn}
go spc.copyToBackend(errc)
go spc.copyFromBackend(errc)
log.Printf("stop conn error: %v\n", <-errc)
}()
}
}
// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
user, backend io.ReadWriter
}
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
_, err := io.Copy(c.user, c.backend)
errc <- err
}
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
_, err := io.Copy(c.backend, c.user)
errc <- err
}

Related

List all Azure container registry images via Golang

I would like to list all images from Azure registry via Golang.
What I founded is this: https://github.com/Azure-Samples/azure-sdk-for-go-samples/tree/main/sdk/resourcemanager/containerregistry but nothing from there seems to help me.
Any ideas please?
LE:
I ended up with this code
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry"
"github.com/Azure/go-autorest/autorest"
)
type ACRTokenProvider struct {
accessToken string
}
func (a *ACRTokenProvider) OAuthToken() string {
return a.accessToken
}
func newACRAuthorizer() (*autorest.BearerAuthorizer, error) {
tenantId := "TENANT_ID"
acrService := "servicename.azurecr.io"
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
panic(err)
}
ctx := context.Background()
aadToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{"https://management.azure.com/"}})
if err != nil {
panic(err)
}
formData := url.Values{
"grant_type": {"access_token"},
"service": {acrService},
"tenant": {tenantId},
"access_token": {aadToken.Token},
}
jsonResponse, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/exchange", acrService), formData)
if err != nil {
panic(err)
}
var response map[string]interface{}
json.NewDecoder(jsonResponse.Body).Decode(&response)
return autorest.NewBearerAuthorizer(&ACRTokenProvider{accessToken: fmt.Sprint(response["refresh_token"])}), nil
}
func main() {
client := containerregistry.NewRepositoryClient("https://servicename.azurecr.io")
authorizer, err := newACRAuthorizer()
if err != nil {
fmt.Println(err)
}
client.Authorizer = authorizer
// Do what you need to do with client here
attributes, err := client.GetList(context.Background(), "registryName", nil)
if err != nil {
log.Printf("Error while fetching attributes, %v ", err)
}
fmt.Print(attributes)
}
But the response is this
Original Error: autorest/azure: Service returned an error. Status=401 Code="Unknown" Message="Unknown service error" Details=[{"errors":[{"code":"UNAUTHORIZED","detail":[{"Action":"*","Name":"catalog","Type":"registry"}],"message":"authentication required, visit https://aka.ms/acr/authorization for more information."}]}]
What I'm missing?
i'm not sure about the GO SDK, but you can always consume the REST API directly:
GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.ContainerRegistry/registries?api-version=2019-05-01
https://learn.microsoft.com/en-us/rest/api/containerregistry/registries/list?tabs=HTTP

Achieving high throughput with Go TCP client-server

I'm going to develop a simple TCP client and server and I want to achieve high throughput (300000 Requests/Second) which is easy to reach with Cpp or C TCP client and server on a server hardware. I mean a server with 48 Cores and 64G Memory.
On my testbed, both client and server have 10G network interface card and I have receive-side-scaling at server side and transmit-packet-steering enabled at the client.
I configure the client to send 10 thousand requests per second. I just run multiple instances of Go go run client.go from a bash script to increase the throughput. However, in this way, Go is going to create lots of threads at the operating systems and a large number of threads results in high context switching cost, and I could not approach such throughputs. I suspected the number of Go instances I'm running from the command line. The code below is the code snippet for the client in the approach:
func Main(cmd_rate_int int, cmd_port string) {
//runtime.GOMAXPROCS(2) // set maximum number of processes to be used by this applications
//var rate float64 = float64(rate_int)
rate := float64(cmd_rate_int)
port = cmd_port
conn, err := net.Dial("tcp", port)
if err != nil {
fmt.Println("ERROR", err)
os.Exit(1)
}
var my_random_number float64 = nextTime(rate) * 1000000
var my_random_int int = int(my_random_number)
var int_message int64 = time.Now().UnixNano()
byte_message := make([]byte, 8)
go func(conn net.Conn) {
buf := make([]byte, 8)
for true {
_, err = io.ReadFull(conn, buf)
now := time.Now().UnixNano()
if err != nil {
return
}
last := int64(binary.LittleEndian.Uint64(buf))
fmt.Println((now - last) / 1000)
}
return
}(conn)
for true {
my_random_number = nextTime(rate) * 1000000
my_random_int = int(my_random_number)
time.Sleep(time.Microsecond * time.Duration(my_random_int))
int_message = time.Now().UnixNano()
binary.LittleEndian.PutUint64(byte_message, uint64(int_message))
conn.Write(byte_message)
}
}
So I try to run all my Go threads by calling go client() in the main so I do not run multiple instances in the Linux command line. I thought it may be a better idea. And it is really a better idea basically and the number of threads doesn't increase toward 700 or so in the operating system. But the throughput still is low and it seems it doesn't employ all capability of the underlying hardware. Actually, you may want to see the code I have run in the second approach:
func main() {
//runtime.GOMAXPROCS(2) // set maximum number of processes to be used by this applications
args := os.Args[1:]
rate_int, _ := strconv.Atoi(args[0])
client_size, _ := strconv.Atoi(args[1])
port := args[2]
i := 0
for i <= client_size {
go client.Main(rate_int, port)
i = i + 1
}
for true {
}
}
I was wondering what is the best practice for in order to reach high throughput? I have always heard that Go is lightweight and performant and pretty comparable with C/Cpp pthread. However, I think in terms of performance still C/Cpp is far far better than Go. I might do something really wrong on this issue, so I would be happy if anybody can help to achieve high throughput with Go.
this is a quick rework of the op code.
As the original source code is working, it does not provide a solution, however it illustrates bucket token usage, and few other small go tips.
It does re use similar default values as op source code.
It demonstrates you do not need two files / programs, to provide both client and server.
It demonstrates usage of flag package.
It shows how to parse unix nano timestamp appropriately using time.Unix(x,y)
It shows how to take advantage of io.Copy to write-what-you-read on the same net.Conn. Rather than manual writing.
Still, this is improper for production delivery.
package main
import (
"encoding/binary"
"flag"
"fmt"
"io"
"log"
"math"
"math/rand"
"net"
"os"
"sync/atomic"
"time"
"github.com/juju/ratelimit"
)
var total_rcv int64
func main() {
var cmd_rate_int float64
var cmd_port string
var client_size int
flag.Float64Var(&cmd_rate_int, "rate", 400000, "change rate of message reading")
flag.StringVar(&cmd_port, "port", ":9090", "port to listen")
flag.IntVar(&client_size, "size", 20, "number of clients")
flag.Parse()
t := flag.Arg(0)
if t == "server" {
server(cmd_port)
} else if t == "client" {
for i := 0; i < client_size; i++ {
go client(cmd_rate_int, cmd_port)
}
// <-make(chan bool) // infinite wait.
<-time.After(time.Second * 2)
fmt.Println("total exchanged", total_rcv)
} else if t == "client_ratelimit" {
bucket := ratelimit.NewBucketWithQuantum(time.Second, int64(cmd_rate_int), int64(cmd_rate_int))
for i := 0; i < client_size; i++ {
go clientRateLimite(bucket, cmd_port)
}
// <-make(chan bool) // infinite wait.
<-time.After(time.Second * 3)
fmt.Println("total exchanged", total_rcv)
}
}
func server(cmd_port string) {
ln, err := net.Listen("tcp", cmd_port)
if err != nil {
panic(err)
}
for {
conn, err := ln.Accept()
if err != nil {
panic(err)
}
go io.Copy(conn, conn)
}
}
func client(cmd_rate_int float64, cmd_port string) {
conn, err := net.Dial("tcp", cmd_port)
if err != nil {
log.Println("ERROR", err)
os.Exit(1)
}
defer conn.Close()
go func(conn net.Conn) {
buf := make([]byte, 8)
for {
_, err := io.ReadFull(conn, buf)
if err != nil {
break
}
// int_message := int64(binary.LittleEndian.Uint64(buf))
// t2 := time.Unix(0, int_message)
// fmt.Println("ROUDNTRIP", time.Now().Sub(t2))
atomic.AddInt64(&total_rcv, 1)
}
return
}(conn)
byte_message := make([]byte, 8)
for {
wait := time.Microsecond * time.Duration(nextTime(cmd_rate_int))
if wait > 0 {
time.Sleep(wait)
fmt.Println("WAIT", wait)
}
int_message := time.Now().UnixNano()
binary.LittleEndian.PutUint64(byte_message, uint64(int_message))
_, err := conn.Write(byte_message)
if err != nil {
log.Println("ERROR", err)
return
}
}
}
func clientRateLimite(bucket *ratelimit.Bucket, cmd_port string) {
conn, err := net.Dial("tcp", cmd_port)
if err != nil {
log.Println("ERROR", err)
os.Exit(1)
}
defer conn.Close()
go func(conn net.Conn) {
buf := make([]byte, 8)
for {
_, err := io.ReadFull(conn, buf)
if err != nil {
break
}
// int_message := int64(binary.LittleEndian.Uint64(buf))
// t2 := time.Unix(0, int_message)
// fmt.Println("ROUDNTRIP", time.Now().Sub(t2))
atomic.AddInt64(&total_rcv, 1)
}
return
}(conn)
byte_message := make([]byte, 8)
for {
bucket.Wait(1)
int_message := time.Now().UnixNano()
binary.LittleEndian.PutUint64(byte_message, uint64(int_message))
_, err := conn.Write(byte_message)
if err != nil {
log.Println("ERROR", err)
return
}
}
}
func nextTime(rate float64) float64 {
return -1 * math.Log(1.0-rand.Float64()) / rate
}
Edit This is a pretty bad answer. Check mh-cbon comments for the reasons.
I don't fully understand how you're trying to do so, but if I want to control the rate on Go, I usually do 2 nested for loops:
for ;; time.Sleep(time.Second) {
go func (){
for i:=0; i<rate; i++ {
go func (){
// Do whatever
}()
}
}()
}
I'm starting a goroutine inside each loop to:
on the outer loop, to ensure it's only 1 second between iterations
on the inner loop, to ensure I can start all the requests I want
Putting this on a problem like yours, it would look something like:
package main
import (
"net"
"os"
"time"
)
const (
rate = 100000
address = "localhost:8090"
)
func main() {
conn, err := net.Dial("tcp", address)
if err != nil {
os.Stderr.Write([]byte(err.Error() + "\n"))
os.Exit(1)
}
for ; err == nil; time.Sleep(time.Second) {
go func() {
for i := 0; i < rate; i++ {
go func(conn net.Conn) {
if _, err := conn.Write([]byte("01234567")); err != nil {
os.Stderr.Write([]byte("\nConnection closed: " + err.Error() + "\n"))
}
}(conn)
}
}()
}
}
To verify that this is actually sending the target request rate, you can have a test TCP listener like this:
package main
import (
"fmt"
"net"
"os"
"time"
)
const (
address = ":8090"
payloadSize = 8
)
func main() {
count := 0
b := make([]byte, payloadSize)
l, err := net.Listen("tcp", address)
if err != nil {
fmt.Fprintf(os.Stdout, "\nCan't listen to address %v: %v\n", address, err)
return
}
defer l.Close()
go func() {
for ; ; time.Sleep(time.Second) {
fmt.Fprintf(os.Stdout, "\rRate: %v/s ", count)
count = 0
}
}()
for {
conn, err := l.Accept()
if err != nil {
fmt.Fprintf(os.Stderr, "\nFailed to accept connection: %v\n", err)
}
for {
_, err := conn.Read(b)
if err != nil {
fmt.Fprintf(os.Stderr, "\nConnection closed: %v\n", err)
break
}
count = count + 1
}
}
}
I found some issues due to not being able to write concurrently into the connection with an error inconsistent fdMutex. This is due to reaching over 0xfffff concurrent writes, which fdMutex does not support. To mitigate this issue, make sure you don't go over that number of concurrent writes. In my system, it was >100k/s. This is not the 300k/s you're expecting, but my system is not prepared for that.

Run Golang as www-data

When I run a Node HTTP server app I usually call a custom function
function runAsWWW()
{
try
{
process.setgid('www-data');
process.setuid('www-data');
} catch (err)
{
console.error('Cowardly refusal to keep the process alive as root.');
process.exit(1);
}
}
from server.listen(8080,'localhost',null,runAsWWW);
so the server is actually running as the www-data user to offer a better modicum of security. Is there something similar I can do when I start up a Golang web server by issuing go run index.go?
No. You can't reliably setuid or setgid in go, because that doesn't work for multithreaded programs.
You need to start the program as the intended user, either directly, through a supervisor of some sort (e.g. supervisord, runit, monit), or through your init system.
Expanding on #JimB's answer:
Use a process supervisor to run your application as a specific user (and handle restarts/crashes, log re-direction, etc). setuid and setgid are universally bad ideas for multi-threaded applications.
Either use your OS' process manager (Upstart, systemd, sysvinit) or a standalone process manager (Supervisor, runit, monit, etc).
Here's an example for Supervisor:
[program:yourapp]
command=/home/yourappuser/bin/yourapp # the location of your app
autostart=true
autorestart=true
startretries=10
user=yourappuser # the user your app should run as (i.e. *not* root!)
directory=/srv/www/yourapp.com/ # where your application runs from
environment=APP_SETTINGS="/srv/www/yourapp.com/prod.toml" # environmental variables
redirect_stderr=true
stdout_logfile=/var/log/supervisor/yourapp.log # the name of the log file.
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
Further: if you're not reverse proxying and your Go application needs to bind to a port < 1024 (e.g. port 80 or 443) then use setcap - for example: setcap cap_net_bind_service=+ep /home/yourappuser/bin/yourapp
PS: I wrote a little article on how to run Go applications with Supervisor (starting from "I don't have Supervisor installed").
You can check if the program is running under a certain user with os/user package:
curr, err := user.Current()
// Check err.
www, err := user.Lookup("www-data")
// Check err.
if *curr != *www {
panic("Go away!")
}
This is not exactly what you want, but it does prevent it from running under any other user. You can run it as www-data by running it with su:
su www-data -c "myserver"
A way to achieve this safely would be to fork yourself.
This is a raw untested example on how you could achieve safe setuid:
1) Make sure you are root
2) Listen on the wanted port (as root)
3) Fork as www-data user.
4) Accept and serve requests.
http://play.golang.org/p/sT25P0KxXK
package main
import (
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/user"
"strconv"
"syscall"
)
var listenFD = flag.Int("l", 0, "listen pid")
func handler(w http.ResponseWriter, req *http.Request) {
u, err := user.Current()
if err != nil {
log.Println(err)
return
}
fmt.Fprintf(w, "%s\n", u.Name)
}
func lookupUser(username string) (uid, gid int, err error) {
u, err := user.Lookup(username)
if err != nil {
return -1, -1, err
}
uid, err = strconv.Atoi(u.Uid)
if err != nil {
return -1, -1, err
}
gid, err = strconv.Atoi(u.Gid)
if err != nil {
return -1, -1, err
}
return uid, gid, nil
}
// FDListener .
type FDListener struct {
file *os.File
}
// Accept .
func (ln *FDListener) Accept() (net.Conn, error) {
fd, _, err := syscall.Accept(int(*listenFD))
if err != nil {
return nil, err
}
conn, err := net.FileConn(os.NewFile(uintptr(fd), ""))
if err != nil {
return nil, err
}
return conn.(*net.TCPConn), nil
}
// Close .
func (ln *FDListener) Close() error {
return ln.file.Close()
}
// Addr .
func (ln *FDListener) Addr() net.Addr {
return nil
}
func start() error {
u, err := user.Current()
if err != nil {
return err
}
if u.Uid != "0" && *listenFD == 0 {
// we are not root and we have no listen fd. Error.
return fmt.Errorf("need to run as root: %s", u.Uid)
} else if u.Uid == "0" && *listenFD == 0 {
// we are root and we have no listen fd. Do the listen.
l, err := net.Listen("tcp", "0.0.0.0:80")
if err != nil {
return fmt.Errorf("Listen error: %s", err)
}
f, err := l.(*net.TCPListener).File()
if err != nil {
return err
}
uid, gid, err := lookupUser("guillaume")
if err != nil {
return err
}
// First extra file: fd == 3
cmd := exec.Command(os.Args[0], "-l", fmt.Sprint(3))
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("cmd.Run error: %s", err)
}
return nil
} else if u.Uid != "0" && *listenFD != 0 {
// We are not root and we have a listen fd. Do the accept.
ln := &FDListener{file: os.NewFile(uintptr(*listenFD), "net")}
if err := http.Serve(ln, http.HandlerFunc(handler)); err != nil {
return err
}
}
return fmt.Errorf("setuid fail: %s, %d", u.Uid, *listenFD)
}
func main() {
flag.Parse()
if err := start(); err != nil {
log.Fatal(err)
}
}

How to make reading and writing to file concurent in Golang?

I setup a webserver and I use my own package where I do some write/read from and to files. When the server gets a tcp connection, I start a different goroutine to handle the request for each connection. In the request handler func, I call the func DoSomething() of some_package.
Here's the code for web_server.go:
package main
import (
sp "./some_package"
"log"
"net"
"os"
"net/http"
)
func main() {
l, err := net.Listen("tcp", "0.0.0.0" + ":" + "4567")
if err != nil {
log.Println("Error listening:", err.Error())
os.Exit(1)
}
defer l.Close()
log.Println("Listening on 0.0.0.0:4567")
go func() {
for {
// Listen for an incoming connection.
conn, err := l.Accept()
if err != nil {
log.Println("Error accepting: ", err.Error())
os.Exit(1)
}
// Handle connections in a new goroutine.
go handlerFunction(conn)
}
}()
log.Printf("Setting up the Webserver...")
err = http.ListenAndServe("0.0.0.0:"+"4568", nil)
if err != nil {
log.Fatal(err)
}
}
func handlerFunction(conn net.Conn) {
defer conn.Close()
sp.DoSomething()
}
The function DoSomething() reads and writes to file. You can see the code where it is declared in the package:
package some_package
import (
"io/ioutil"
"strconv"
"os"
"log"
)
func IncrementValue(pastValue string)(newValue string){
newValueInt, _ := strconv.Atoi(pastValue)
return strconv.Itoa(newValueInt + 1)
}
func DoSomething() (err error){
initialValue := "1"
filename := "myFile.txt"
if _, err := os.Stat(filename); err == nil {
someText, err := ioutil.ReadFile(filename)
if err != nil {
log.Printf("Error reading")
return err
}
newValue := IncrementValue(string(someText))
err = ioutil.WriteFile(filename,[]byte(newValue), 0644)
if err != nil {
return err
}
}else{
err = ioutil.WriteFile(filename,[]byte(initialValue), 0644)
if err != nil {
return err
}
}
return
}
How can I use a locking mechanism like mutex.Lock and mutex.Unlock in this case to make the reading and writing to file concurrent so when one routine which is currently writing can stop the other from reading till the first one writes to file successfully?
Is my example suitable to be concurrent when reading or writing to file?
Is this the right approach to do so? Thank You
You can't make the reading and writing of a file concurrent (well, it's possible, but not with the access pattern you're describing). Use a single mutex to serialize all access to your file:
var fileMutex sync.Mutex
func DoSomething() {
fileMutex.Lock()
defer fileMutex.Unlock()
//...
}

How to feed a password to SSH?

I need to use password authenticated scp to download a file from a server. How do I do so using Go? Tried the following code, but it doesn't pass in the password.
package main
import (
"os/exec"
"time"
)
func main() {
password := "password"
cmd := exec.Command("scp", "admin#192.168.1.150:file", "file")
in, err := cmd.StdinPipe()
if err != nil {
panic(err)
}
defer in.Close()
out, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
defer out.Close()
if err = cmd.Run(); err != nil {
panic(err)
}
go func() {
time.Sleep(10 * time.Second)
_, err = in.Write([]byte(password + "\n"))
if err != nil {
panic(err)
}
}()
}
Edit: I ended up using the gexpect (github.com/ThomasRooney/gexpect) library.
package main
import (
"github.com/ThomasRooney/gexpect"
"log"
)
func main() {
child, err := gexpect.Spawn("scp admin#192.168.1.150:file file")
if err != nil {
log.Fatalln(err)
}
child.Expect("password:")
child.SendLine("password")
child.Interact()
child.Close()
}
The answer to this self-answered question might help:
Golang write input and get output from terminal process
at least, he mentions in the answer that he "was able to get ssh access working with a password", which is not mentioned explicitly in the question - that's why you probably didn't find it while searching the site?

Resources