SFTP In Go Error: User does not have appropriate read permission - go

I am trying to upload a product feed to a Google Merchant SFTP account. I am able to upload a file manually through the command prompt but encounter the following error when trying to do it through Go.
Error: sftp: "User does not have appropriate read permission." (SSH_FX_PERMISSION_DENIED)
I am using the github.com/pkg/sftp package, following the example in https://godoc.org/github.com/pkg/sftp#Client.Open. I suspect that the Create/Write pattern here ends up being different from a simple put from command line.
Code
func (g *GoogleExporter) ExportToSFTP(file []byte) error {
// Creating an SSH connection
sshConfig := &ssh.ClientConfig{
User: g.Creds.AccessData.SFTPUser,
Auth: []ssh.AuthMethod{
ssh.Password(g.Creds.AccessData.SFTPPassword),
},
}
hostPort := fmt.Sprintf("%s:%d", SFTPHostName, SFTPHostPort)
connection, err := ssh.Dial("tcp", hostPort, sshConfig)
if err != nil {
return err
}
fmt.Println(">> SSH Connection Created!")
// Creating an SFPT connection over SSH
sftp, err := sftp.NewClient(connection)
if err != nil {
return err
}
defer sftp.Close()
fmt.Println(">> SFTP Client Created!")
// Uploading the file
remoteFileName := "products.xml" // TODO: Make this name configurable
remoteFile, err := sftp.Create(remoteFileName)
if err != nil {
return err
}
fmt.Println(">> SFTP File Created!")
if _, err := remoteFile.Write(file); err != nil {
return err
}
fmt.Println("Successfully uploaded product feed to SFTP, file:%s user:%s", remoteFileName, g.Creds.AccessData.SFTPUser)
util.Log("Successfully uploaded product feed to SFTP, file:%s user:%s", remoteFileName, g.Creds.AccessData.SFTPUser)
// Confirming if the file is there
if _, err := sftp.Lstat(remoteFileName); err != nil {
return err
}
return nil
}
The error is cause by this line:
remoteFile, err := sftp.Create(remoteFileName)

I am answering my own question to help anyone else that is having this problem. I was able to find a solution.
The Google Merchant SFTP account only gives you write only access. However, according to the docs, when using the sftp.Create(..) function, it creates a file with the flags as 0666, which does not agree with the permissions set on your user.
To mimic the behavior of sftp.Create(..) with write only permissions, you can use the more general sftp.OpenFile(..) function.
remoteFile, err := sftp.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
The flags os.O_WRONLY|os.O_CREATE|os.O_TRUNC will mimic the behavior of Create() i.e. create a file it doesn't exist and truncate the file if it does.

Related

Golang windows service not writting to logfile

I have a simple TCP-server written in Go and running as windows service. It installs/uninstalls just fine, it is working as service and doing it's job but it's not writing a single word in a log file. When I run it simply as an executable in a console - logging works fine. But when it starts as a service - log file is created, application process opens it (thus disallowing to delete it for example) but file remains empty. What I'm doing wrong?
Any word of advice is appreciated.
logging is confured like this
func (p *Program) run() {
exec, _ := os.Executable()
WORK_DIR = filepath.Join(filepath.Dir(exec), "ATT")
_, err := checkDir(WORK_DIR)
if err != nil {
panic("Не удалось создать рабочий каталог")
}
f, err := os.OpenFile(filepath.Join(filepath.Dir(exec), "server.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Не удалось создать лог-файл: %v", err)
}
defer f.Close()
wrt := io.MultiWriter(os.Stdout, f)
log.SetOutput(wrt)
server, err := net.Listen("tcp", ":"+PORT)
if err != nil {
log.Fatal(err)
}
defer server.Close()
log.Println("Сервер запущен на порту:", PORT)
for {
connection, err := server.Accept()
if err != nil {
log.Println("Не удалось установить соединение с клиентом", err)
}
go HandleServer(connection)
}
}
The issue you're encountering with logging can be related to file permissions when running as a service. When a service starts, it runs with a different set of permissions than when it runs as a regular executable. In some cases, the service might not have permission to write to a log file in the same location as the executable.
One possible solution is to specify a different location for the log file, such as C:\Windows\System32\LogFiles, which is a common location for log files in Windows. You could also try specifying the absolute path to the log file in the OpenFile call to avoid any confusion about the working directory.
Another potential issue could be with the defer statement that closes the log file. When the service terminates, any deferred statements will be executed, which could cause the log file to be closed before all log messages have been written. To avoid this, you could remove the defer statement and manually close the log file when the service stops.
It may also be a good idea to add error handling to the logging calls to ensure that all log messages are being written even if the log file becomes unavailable. Here is an example:
func (p *Program) run() {
exec, _ := os.Executable()
WORK_DIR = filepath.Join(filepath.Dir(exec), "ATT")
_, err := checkDir(WORK_DIR)
if err != nil {
panic("Не удалось создать рабочий каталог")
}
// Specify the absolute path for the log file
logFilePath := filepath.Join(filepath.Dir(exec), "server.log")
f, err := os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Не удалось создать лог-файл: %v", err)
}
// Remove the defer statement to close the log file manually later
// defer f.Close()
wrt := io.MultiWriter(os.Stdout, f)
log.SetOutput(wrt)
server, err := net.Listen("tcp", ":"+PORT)
if err != nil {
log.Fatal(err)
}
defer server.Close()
log.Println("Сервер запущен на порту:", PORT)
for {
connection, err := server.Accept()
if err != nil {
log.Println("Не удалось установить соединение с клиентом", err)
}
go HandleServer(connection)
}
// Manually close the log file when the service stops
f.Close()
}
// Add error handling to the logging calls
func logMessage(message string) {
_, logErr := log.Println(message)
if logErr != nil {
fmt.Printf("Error logging message: %v\n", logErr)
}
}
I hope these suggestions help you resolve the issue with logging in your service. Please let me know.

How can I make my GCloud Function open a new SSH connection to consume a SFTP server?

My setup requires a Google Function to do some stuff and upload the result to a SFTP server. I'm currently using the basic sftp and crypto/ssh packages to achieve this. Locally, after some debugging, I was able to retrieve the server's pubkey.
When deploying to GCloud nothing works, of course.
This is what handles the connection on my function
func Connect(host string, port string, user string, password string) (*ssh.Client, error) {
hostKey := getHostKey(host)
var auths []ssh.AuthMethod
// Use password authentication if provided
if password != "" {
auths = append(auths, ssh.Password(password))
}
config := &ssh.ClientConfig{
User: user,
HostKeyCallback: ssh.FixedHostKey(hostKey),
Auth: auths,
}
cipherOrder := config.Ciphers
config.Ciphers = append(cipherOrder, "aes128-cbc", "3des-cbc")
sshConn, err := ssh.Dial("tcp", host+":"+port, config)
if err != nil {
return nil, err
}
return sshConn, nil
}
func getHostKey(host string) ssh.PublicKey {
file, err := os.Open("/root/.ssh/known_hosts")
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read known_hosts file: %v\n", err)
os.Exit(1)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing %q: %v\n", fields[2], err)
os.Exit(1)
}
break
}
}
if hostKey == nil {
fmt.Fprintf(os.Stderr, "No hostkey found for %s", host)
os.Exit(1)
}
return hostKey
}
The known_hosts file doesn't exist. I don't have the pubkey of the server, but with Filezilla I can connect to it just fine.
I had to specify those cyphers because a barebone ssh hostname would return Unable to negotiate... error
Is there any other way to do this? I'm thinking about uploading my own known_hosts file but it doesn't sound like a great solution.
I was probably over engineering it.
Setting the ssh.ClientConfig like this solved the problem:
config := &ssh.ClientConfig{
User: user,
Auth: auths,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
Anyway I also found a nicer package to easily handle SSH connection, simplessh.
conn, _ := simplessh.ConnectWithPassword(host, user, pass)
client, _ := sftp.NewClient(conn.SSHClient)

Open a Micrsoft DFS share in golang using smb2

I have a dfs share on Windows server 2012. I can open the share and read files from another system in a windows file explorer with a path like: \\dfsserver\usernamepace\Tom\go.png. However, using golang's smb2 package by https://github.com/hirochachacha/go-smb2 I get an error as
response error: The contacted server does not support the indicated
part of the DFS namespace
But if I try to open the file with it's actual share path \\dfsserver\public share\Tom\go.png then the code works fine. So the problem is that I have no knowledge of the actual path during runtime and I want to be able to open the file with path provided by DFS.
Could it be the case that DFS does not work properly with smb2? or some other issues. Thanks in advance for your comments.
func main(){
// actualPath := `\\dfsserver\public share\Tom\go.png`
// dfsPath := `\\dfsserver\usernamespace\Tom\go.png`
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", "dfsserver", "445"))
if err != nil {
fmt.Println(err)
}
defer conn.Close()
dial := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: "user",
Password: "password",
Domain: "dfsserver",
},
}
session, err := dial.Dial(conn)
if err != nil {
fmt.Println(err)
}
defer session.Logoff()
mountPoint, err := session.Mount("usernamespace")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// error occures here, if the mountPoint was "public share" instead of "usernamespace" then no error
remoteFile, err := mountPoint.Open(`Tom\go.png`)
defer remoteFile.Close()
if err != nil {
fmt.Println(err)
}
}

GoLang SSH Commands with slashes seem to instantly fail

I'm currently writing an app that given a parameter, will run a command on a remote server. I'm using the /x/crypto/ssh package, everything seems to go smoothly if I use one liner commands like "who" or "ls", however, if I run a more complex command such as:
"grep SOMEDATA /var/log/logfile.log"
the program immediately exits and the command execution line with nothing more than "process exited with status 1", and I don't get anything else back.
If I check the history of the user I'm having it SSH into the remote system as, I do not see the command running at all.
Has anyone else run into this type of issue before? Here's a snippet of the code I'm using to execute this (sensitive data removed of course):
func returnData(w http.ResponseWriter, r *http.Request) {
var b bytes.Buffer
hostKey, err := getHostKey("SERVERNAME")
if err != nil {
log.Fatalln(err)
}
err = r.ParseForm()
if err != nil {
log.Fatalln(err)
}
config := &ssh.ClientConfig{
User: "USERNAME",
Auth: []ssh.AuthMethod{
ssh.Password("TESTPASS"),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
client, err := ssh.Dial("tcp", "SERVERNAME:22", config)
if err != nil {
log.Fatalln("Creating Client Failed: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatalln("Creating new Session Failed: ", err)
}
session.Stdout = &b
inputData := r.Form["fname"][0]
cmdExecute := fmt.Sprintf(`sudo grep %v /var/log/logfile.log`, inputData)
log.Println(cmdExecute)
if err := session.Run(cmdExecute); err != nil {
log.Fatalln("Getting Data From session Failed: ", err)
log.Fatalln(b.String())
}
//log.Println(hostKey)
defer session.Close()

cloud storage: unable to upload any content while local with golang

I have this piece of code:
ctx:=context.Background()
cliente, err := storage.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
clienteCS := cliente.Bucket("prueba123456789")
w:=clienteCS.Object("prueba").NewWriter(ctx)
w.ContentType = "text/plain"
if _, err := w.Write([]byte("abcde\n")); err != nil {
log.Fatal(err)
}
attrs, err := clienteCS.Attrs(ctx)
fmt.Printf("atrr: %+v err:%v\n",attrs,err)
When I run this program, in the console I can see the attributes printed from my bucket, that would mean for me that there is nothing wrong with the configuration
, but the problem is that when I see on console.cloud.google.com I see no file on my bucket even on api dashboard does not seem like any api has been called(that peak before the red line is other api I was using) , even though no error is showing on my console that could indicate something went wrong
I just forgot about closing the client, this page alert this problem, always must close the client or the file won't be send, so just need to add some lines:
if err := w.Close(); err != nil {
log.Fatal(err)
return
}

Resources