在GO中存储基本的HTTP AUth用户/密码凭据,无需使用外部包。

huangapple go评论74阅读模式
英文:

Store Basic HTTP AUth user/password credentials in GO without external packages

问题

我正在使用标准库(以及mysql驱动程序)开发一个简单的Go博客引擎。

对于管理员,我正在使用基本的HTTP身份验证。

func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
    u, p, ok := r.BasicAuth()
    if !ok {
        w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM!"`)
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
        return false
    }

    if u != "devnull" || p != "veryfancypw" {
        w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM!"`)
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
        return false
    }

    return true
}

显然,这种方式并不理想,因为用户密码不应该以这种方式硬编码。

最佳的存储这些凭据的方式是什么?例如,将其存储在配置文件中,但不使用外部包?在运行go run main.go时将其作为参数传递是否可行?

更新:

我接受了@stdtom的回答,因为它非常全面。我选择将凭据存储在环境变量中,所以我们有:

func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
    u, p, ok := r.BasicAuth()
    boss := os.Getenv("BOSS")
    bosspw := os.Getenv("BOSSPW")

    // printf("Debug: %s, debugging: %s\n", boss, bosspw)

    if !ok {
        w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM!"`)
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
        return false
    }

    if u != boss || p != bosspw {
        w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM!"`)
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
        return false
    }

    return true
}

为了使其工作,您需要在.zshrc(在Mac上)或者如果您使用bash,则在.bashrc中添加这些变量:

export BOSS=myusername
export BOSSPW="my long and difficult to get password"

正如接受的回答所建议的那样,"特权用户可以访问环境变量...",但是我的场景是基于您在自己的机器上运行,所以如果有人能够访问它,您的博客管理界面的用户名/密码可能是您最不用担心的问题。

我认为这非常有用,因为您在网上找到的大多数关于在Go中使用基本的HTTP身份验证的示例都只显示了硬编码的用户名和密码,这当然是一个非常糟糕的主意。

英文:

I am developing a simple blog engine in go using only the standard libraries (and the mysql driver 😁)

For the admin I am using Basic HTTP Auth

func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
	u, p, ok := r.BasicAuth()
	if !ok {
		w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
		w.WriteHeader(401)
		w.Write([]byte("401 Unauthorized\n"))
		return false
	}

	if u != "devnull" || p != "veryfancypw" {
		w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
		w.WriteHeader(401)
		w.Write([]byte("401 Unauthorized\n"))
		return false
	}

	return true
}

This is obviously not ideal as the user pw should not be hardcoded this way.

What is the best way to store these credentials e.g. in a config file but without external packages ? Is it desirable to pass it as a parameter when I launch go run main.go or ?

UPDATE

I accepted @stdtom reply as it is very comprehensive. I opted for storing in the environmental variables so we have:

func IsAllowed(w http.ResponseWriter, r *http.Request) bool {
	u, p, ok := r.BasicAuth()
	boss := os.Getenv("BOSS")
	bosspw := os.Getenv("BOSSPW")

	// printf("Debug: %s, debugging: %s\n", boss, bosspw)

	if !ok {
		w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
		w.WriteHeader(401)
		w.Write([]byte("401 Unauthorized\n"))
		return false
	}

	if u != boss || p != bosspw {
		w.Header().Set("WWW-Authenticate", `Basic realm="Beware! Protected REALM! "`)
		w.WriteHeader(401)
		w.Write([]byte("401 Unauthorized\n"))
		return false
	}

	return true
}

For this to work you need to add these variables in .zshrc (on a mac) or if you use bash .bashrc

export BOSS=myusername
export BOSSPW="my long and difficult to get password"

As the accepted reply suggests "Privileged users can access the environment variables..." but my scenario is based on your running your own machine so if someone gets access to it the user/pw to the admin interface of your blog is probably the least of your problems.

I think this was very useful as most example you find online about using Basic HTTP Auth in GO just show hardcoded username and passwords which is of course a very bad idea.

答案1

得分: 1

当涉及将凭据存储在服务器或其他运行环境中时,你会陷入两难境地。没有真正好用的解决方案。

首先要问自己的是,你的威胁模型是什么。

  • A: 凭据被持久化存储在版本控制中,与他人共享,甚至更糟的是在GitHub等地方公开。
  • B: 凭据暴露给运行环境的非特权共享用户。
  • C: 凭据暴露给运行环境的特权用户(包括入侵系统并能够获得特权用户权限的攻击者)。

根据定义的威胁,你可以开始评估存储和注入凭据的潜在解决方案。当然,这将取决于你的环境(例如操作系统、云提供商、Kubernetes/Docker等)。以下我将假设操作系统为Linux。

作为参数传入
可以减轻威胁A,但无法减轻威胁B和C。命令行参数甚至可以被非特权用户通过ps -eo args等方式获取。

存储在配置文件中
可以减轻威胁B,前提是文件权限设置正确。关于威胁A,仍然存在配置文件意外添加到版本控制的风险。无法减轻威胁C。

如果你使用json格式的配置文件,可以很容易地使用Golang标准库实现。

存储在环境变量中
可以减轻威胁A和B,但无法减轻威胁C。特权用户可以通过/proc/<pid>/environ访问环境变量。此外,如何在运行环境中设置环境变量仍然是一个问题。如果你使用CI/CD流水线部署服务,可以在部署过程中使用该流水线注入环境变量。通常,CI/CD引擎都带有某种用于存储密钥的变量存储。

这种方法的缺点是环境变量是临时的,因此在运行环境重新启动后,你需要通过CI/CD流水线重新部署,或者需要确保在运行环境中保持凭据的持久性,例如在启动脚本中。

可以使用标准库中的os.Getenv()os.LookupEnv()轻松读取环境变量。

手动输入启动时的凭据
可以减轻威胁A和B,但特权用户仍然可以从内存中读取凭据。在运行环境重新启动后,服务将不可用,直到操作员手动输入凭据。因此,在许多情况下,这种方法可能被认为是不实际的。

进一步考虑

  • 将凭据存储在数据库中,如brianmac建议的,会将问题转移到“在哪里存储我的数据库凭据?”

  • 将密钥加密与上述任何解决方案结合使用将要求在运行环境中向服务提供解密密钥。因此,你需要一个基于TPM的解决方案,或者你面临一个问题,即在哪里存储这个密钥。

  • “作为服务的凭据”解决方案,如Hashicorp Vault、Azure Key Vault、AWS Secrets Manager等,在你的场景中可能过于庞大。它们提供了集中存储和管理凭据的功能。应用程序/服务可以通过定义的API从该解决方案中检索凭据。

    然而,这需要对请求凭据的服务进行身份验证和授权。因此,我们又回到了如何在运行环境中存储另一个服务的凭据的问题。

    云提供商试图通过为运行环境分配身份并授权该身份访问其他云资源(包括“作为服务的凭据”解决方案)来克服这个问题。通常,只有指定的运行环境才能检索身份的凭据。然而,没有什么能阻止具有对运行环境访问权限的特权用户使用该身份来访问凭据。

总之,以一种特权用户或入侵系统的人无法访问凭据的方式存储凭据是困难到不可能的。

如果你接受这作为剩余风险,将凭据存储在环境变量中是一个很好的方法,因为它可以避免持久化凭据。它也是平台无关的,因此可以与任何运行环境、云提供商等一起使用。它还可以由各种自动化和部署工具支持。

英文:

When it comes down to storing credentials on a server or other runtime environment, you are somehow between the devil and the deep blue sea. There is no real good solution which is likewise usable.

Start asking yourself, what your threat model is.

  • A: Secrets being persisted in version control, shared with others, or even worse, made public on GitHub etc.
  • B: Secrets being exposed to unprivileged co-users of the runtime environment
  • C: Secrets being exposed to privileged users of the runtime environment (including an attacker who compromised the system and was able to get privileged user rights).

Based on the threats defined, you can start assessing potential solutions to store and inject secrets. This will of course depend on your environment (e.g. OS, cloud provider, Kubernetes/Docker, etc.). In the following I will assume Linux as OS.

Pass in as parameter:
Would mitigate threat A, but not B and C. Command line arguments can be revealed even by unprivileged users e.g. by ps -eo args

Store in config file:
Would mitigate threat B, given that file permissions are set correctly. With regard to A, there is still a risk that the config file is unintendedly added to the version control. Does not mitigate threat C.

If you would use e.g. json format for the config file, this could be implemented easily with the Golang standard lib.

Store in environment variables:
Would mitigate threats A and B, but not C. Privileged users can access the environment variables via /proc/&lt;pid&gt;/environ. Also the question remains how you will set the environment variables in the runtime environment. If you are using a CI/CD pipeline to deploy your service, this pipeline could be used to inject the environment variables during deployment. Usually, the CI/CD engine come with some kind of variable store for secrets.

Drawback of this approach is that the environment variables will be ephemeral, so after a reboot of the runtime environment you would need to redeploy via the CI/CD pipeline or you need to ensure persistence of the secrets in the runtime environment, e.g. in a startup script.

Environment variables can be read easily with os.Getenv() or os.LookupEnv() from the standard lib.

Enter manually on start time:
Would mitigate A and B, but privileged users would still be able to read the secrets from memory. Upon reboot of the runtime environment, the service will not be available until an operator enters the secrets manually. So this approach would probably be considered as impractical in many use cases.

Further considerations:

  • Storing secrets in a database as suggested by brianmac shifts the question to "Where to store my db credentials?"

  • Combining secret encryption with any of the solutions described above will require that the decryption key is made available to the service in the runtime environment. So you either need a TPM-based solution or you are faced with the question, where to store this key.

  • "Secrets as a Service" solutions like Hashicorp Vault, Azure Key Vault, AWS Secrets Manager etc. will probably be oversized in your scenarion. They provide centralized storage and management of secrets. Applications/services can retrieve secrets from this solution via a defined API.

    This, however, requires authentication and authorization of the service requesting a secret. So we are back at the question how to store another secret for the service in there runtime environment.

    Cloud providers try to overcome this by assigning the runtime environment an identity and authorizing this identity to access other cloud resources including the "Secret as a Service" solution. Usually only the designated runtime environment will be able to retrieve the credentials of the identity. However, nothing can prevent an privileged user who has access the runtime environment from using the identity to access the secrets.

Bottom line is that it is hard to impossible to store secrets in a way that a privileged user or someone who compromised the system will not be able to get access.

If you accept this as the residual risk, storing the secrets in environment variables is a good approach as it can avoid persisting secrets. It is also platform agnostic and thus can be used with any runtime environment, cloud provider etc. It can also be supported by a variety of automation and deployment tools.

huangapple
  • 本文由 发表于 2022年4月14日 17:41:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/71869546.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定