Compare commits
7 Commits
dependabot
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564ee12988 | ||
| 14cbe3b9f2 | |||
| fe3f8885c6 | |||
| 760ae5cf8d | |||
| a5be141bac | |||
| 7ac73b9489 | |||
| f72a4416e3 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
go.sum
|
||||
node_modules/
|
||||
package-lock.json
|
||||
9
Makefile
9
Makefile
@ -26,7 +26,7 @@ ifneq ($(GIT_TAG),)
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: mod build
|
||||
all: mod build deb
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# build
|
||||
@ -38,6 +38,10 @@ $(BINDIR)/$(BINNAME): $(SRC)
|
||||
go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/rdpgw
|
||||
go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME2) ./cmd/auth
|
||||
|
||||
.PHONY: deb
|
||||
deb:clean mod build
|
||||
dpkg-buildpackage -b -us -uc
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# install
|
||||
|
||||
@ -50,7 +54,7 @@ install: build
|
||||
|
||||
.PHONY: mod
|
||||
mod:
|
||||
go mod tidy -compat=1.22
|
||||
go mod tidy -compat=1.23
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# test
|
||||
@ -64,6 +68,7 @@ test:
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -rf '$(BINDIR)' ./_dist
|
||||
dh_clean
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
|
||||
119
README.md
119
README.md
@ -434,3 +434,122 @@ flexibility.
|
||||
|
||||
# Acknowledgements
|
||||
* This product includes software developed by the Thomson Reuters Global Resources. ([go-ntlm](https://github.com/m7913d/go-ntlm) - BSD-4 License)
|
||||
|
||||
# RDPGW 认证 API 服务器
|
||||
|
||||
这是一个用于 RDPGW(远程桌面网关)的认证 API 服务器,提供用户验证和密码获取功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持用户名和密码验证(verify 模式)
|
||||
- 支持密码检索功能(getpassword 模式),用于 NTLM 认证
|
||||
- 支持 GET 和 POST 请求方式
|
||||
- 可通过配置文件自定义设置
|
||||
|
||||
## 安装
|
||||
|
||||
确保已安装 Node.js v10 或更高版本,然后执行:
|
||||
|
||||
```bash
|
||||
npm install express
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
配置文件为 `config.json`,包含以下选项:
|
||||
|
||||
```json
|
||||
{
|
||||
"port": 3000,
|
||||
"apiPath": "/api/checkperm",
|
||||
"users": {
|
||||
"testuser": "testpassword",
|
||||
"admin": "adminpass"
|
||||
},
|
||||
"logger": {
|
||||
"level": "info",
|
||||
"logToFile": false,
|
||||
"logFile": "server.log"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 配置选项说明
|
||||
|
||||
- `port`: 服务器监听端口
|
||||
- `apiPath`: API 路径
|
||||
- `users`: 用户名和密码字典
|
||||
- `logger`: 日志配置
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 启动服务器
|
||||
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
### API 请求示例
|
||||
|
||||
#### 验证模式 (verify)
|
||||
|
||||
**GET 请求**:
|
||||
```
|
||||
http://localhost:3000/api/checkperm?username=testuser&password=testpassword&mode=verify
|
||||
```
|
||||
|
||||
**POST 请求**:
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/checkperm \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"testuser", "password":"testpassword", "mode":"verify"}'
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"user": "testuser"
|
||||
}
|
||||
```
|
||||
|
||||
#### 密码获取模式 (getpassword)
|
||||
|
||||
**GET 请求**:
|
||||
```
|
||||
http://localhost:3000/api/checkperm?username=testuser&mode=getpassword
|
||||
```
|
||||
|
||||
**POST 请求**:
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/checkperm \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"testuser", "mode":"getpassword"}'
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"password": "testpassword"
|
||||
}
|
||||
```
|
||||
|
||||
## RDPGW 集成
|
||||
|
||||
在 RDPGW 配置中添加以下内容:
|
||||
|
||||
```yaml
|
||||
ntlm_api:
|
||||
enable: true
|
||||
server: http://localhost:3000
|
||||
path: /api/checkperm
|
||||
mode: getpassword
|
||||
```
|
||||
|
||||
## 安全建议
|
||||
|
||||
- 在生产环境中,请使用 HTTPS 而非 HTTP
|
||||
- 限制 API 服务器的访问权限
|
||||
- 定期更换密码并审查访问日志
|
||||
- 考虑实现 IP 访问限制和请求频率限制
|
||||
|
||||
@ -133,8 +133,19 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
server := grpc.NewServer()
|
||||
db := database.NewConfig(conf.Users)
|
||||
|
||||
// 根据配置选择使用API认证或本地配置认证
|
||||
var db database.Database
|
||||
if conf.PXVDI.Enabled && conf.PXVDI.ApiUrl != "" {
|
||||
log.Printf("Using API authentication, API URL: %s", conf.PXVDI.ApiUrl)
|
||||
db = database.NewApiDb(conf.PXVDI.ApiUrl, conf.PXVDI.ApiKey)
|
||||
} else {
|
||||
log.Printf("Using local configuration file authentication")
|
||||
db = database.NewConfig(conf.Users)
|
||||
}
|
||||
|
||||
service := NewAuthService(opts.ServiceName, db)
|
||||
auth.RegisterAuthenticateServer(server, service)
|
||||
server.Serve(listener)
|
||||
|
||||
@ -11,6 +11,13 @@ import (
|
||||
|
||||
type Configuration struct {
|
||||
Users []UserConfig `koanf:"users"`
|
||||
PXVDI PXVDIConfig `koanf:"pxvdi"`
|
||||
}
|
||||
|
||||
type PXVDIConfig struct {
|
||||
Enabled bool `koanf:"enabled"`
|
||||
ApiUrl string `koanf:"apiurl"`
|
||||
ApiKey string `koanf:"apikey"`
|
||||
}
|
||||
|
||||
type UserConfig struct {
|
||||
@ -36,6 +43,7 @@ func Load(configFile string) Configuration {
|
||||
|
||||
koanfTag := koanf.UnmarshalConf{Tag: "koanf"}
|
||||
k.UnmarshalWithConf("Users", &Conf.Users, koanfTag)
|
||||
k.UnmarshalWithConf("PXVDI", &Conf.PXVDI, koanfTag)
|
||||
|
||||
return Conf
|
||||
|
||||
|
||||
181
cmd/auth/database/apidb.go
Normal file
181
cmd/auth/database/apidb.go
Normal file
@ -0,0 +1,181 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ApiDb 结构实现Database接口,通过API验证用户凭据
|
||||
type ApiDb struct {
|
||||
ApiUrl string // API URL模板,使用{username}和{password}作为占位符
|
||||
ApiKey string // API密钥
|
||||
}
|
||||
|
||||
// NewApiDb 创建一个新的ApiDb实例
|
||||
func NewApiDb(apiUrl string, apiKey string) *ApiDb {
|
||||
return &ApiDb{
|
||||
ApiUrl: apiUrl,
|
||||
ApiKey: apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPassword 从API获取用户密码
|
||||
// 这个方法会调用API来获取用户的实际密码,用于NTLM认证
|
||||
func (a *ApiDb) GetPassword(username string) string {
|
||||
log.Printf("Getpassword: %s", username)
|
||||
|
||||
// 如果用户名为空,直接返回失败
|
||||
if username == "" {
|
||||
log.Printf("API password retrieval failed: empty username")
|
||||
return ""
|
||||
}
|
||||
|
||||
// 构建API URL
|
||||
fullUrl := fmt.Sprintf("%s/api/custom/public/ntlmcheck", a.ApiUrl)
|
||||
|
||||
// 构建POST请求体
|
||||
requestData := map[string]string{
|
||||
"user": username,
|
||||
"apikey": a.ApiKey,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(requestData)
|
||||
if err != nil {
|
||||
log.Printf("Failed to marshal request data: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
log.Printf("Sending API password POST request to: %s", fullUrl)
|
||||
|
||||
// 创建自定义HTTP客户端,跳过SSL证书验证
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
// 发送POST请求到API,使用不验证SSL证书的客户端
|
||||
resp, err := client.Post(fullUrl, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("API password retrieval error: %v", err)
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查HTTP状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("API password retrieval failed with status: %d", resp.StatusCode)
|
||||
return ""
|
||||
}
|
||||
|
||||
// 读取响应内容
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read API response: %v", err)
|
||||
return ""
|
||||
}
|
||||
log.Printf("API password response received")
|
||||
|
||||
// 解析响应 - 适应新的响应格式
|
||||
var result struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
Pass string `json:"pass"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(bytes.NewReader(body)).Decode(&result)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse API response: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// 检查响应内容
|
||||
if !result.Success || result.Data.Pass == "" {
|
||||
log.Printf("API did not return a valid password for user: %s", username)
|
||||
return ""
|
||||
}
|
||||
|
||||
log.Printf("API password retrieval successful for user: %s", username)
|
||||
return result.Data.Pass
|
||||
}
|
||||
|
||||
// VerifyCredentials 验证用户凭据
|
||||
func (a *ApiDb) VerifyCredentials(username, password string) bool {
|
||||
// 如果用户名为空,直接返回失败
|
||||
if username == "" {
|
||||
log.Printf("API verification failed: empty username")
|
||||
return false
|
||||
}
|
||||
|
||||
// 构建API URL,替换占位符
|
||||
apiUrl := a.ApiUrl
|
||||
|
||||
// 构建完整的URL,包括查询参数
|
||||
var fullUrl string
|
||||
if password == "" {
|
||||
// NTLM场景下直接将用户名传递给API
|
||||
// 这种情况下,后端API应当能够独立验证用户
|
||||
fullUrl = fmt.Sprintf("%s?username=%s&mode=verify",
|
||||
apiUrl, url.QueryEscape(username))
|
||||
log.Printf("Verifying NTLM user via API: %s", username)
|
||||
} else {
|
||||
// 常规场景
|
||||
fullUrl = fmt.Sprintf("%s?username=%s&password=%s&mode=verify",
|
||||
apiUrl, url.QueryEscape(username), url.QueryEscape(password))
|
||||
}
|
||||
|
||||
log.Printf("Sending API verification request to: %s", fullUrl)
|
||||
|
||||
// 创建自定义HTTP客户端,跳过SSL证书验证
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
// 发送请求到API,使用不验证SSL证书的客户端
|
||||
resp, err := client.Get(fullUrl)
|
||||
if err != nil {
|
||||
log.Printf("API verification error: %v", err)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查HTTP状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("API verification failed with status: %d", resp.StatusCode)
|
||||
return false
|
||||
}
|
||||
|
||||
// 读取响应内容
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read API response: %v", err)
|
||||
return false
|
||||
}
|
||||
log.Printf("API response: %s", string(body))
|
||||
|
||||
// 解析响应
|
||||
var result map[string]interface{}
|
||||
err = json.NewDecoder(bytes.NewReader(body)).Decode(&result)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse API response: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查响应内容
|
||||
// 如果响应包含"success",则验证成功
|
||||
if status, ok := result["status"].(string); ok && status == "success" {
|
||||
log.Printf("API verification successful for user: %s", username)
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果没有找到预期的成功标识,则验证失败
|
||||
log.Printf("API verification failed for user: %s", username)
|
||||
return false
|
||||
}
|
||||
@ -140,13 +140,16 @@ func (c *ntlmContext) authenticate(am *ntlm.AuthenticateMessage, r *auth.NtlmRes
|
||||
}
|
||||
|
||||
username := am.UserName.String()
|
||||
password := c.h.Database.GetPassword (username)
|
||||
log.Printf("NTLM: Trying to validate user: %s", username)
|
||||
|
||||
password := c.h.Database.GetPassword(username)
|
||||
if password == "" {
|
||||
log.Printf("NTLM: unknown username specified: %s", username)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.session.SetUserInfo(username,password,"")
|
||||
log.Printf("NTLM: Successfully retrieved password for user: %s", username)
|
||||
c.session.SetUserInfo(username, password, "")
|
||||
|
||||
err := c.session.ProcessAuthenticateMessage(am)
|
||||
if err != nil {
|
||||
@ -156,5 +159,6 @@ func (c *ntlmContext) authenticate(am *ntlm.AuthenticateMessage, r *auth.NtlmRes
|
||||
|
||||
r.Authenticated = true
|
||||
r.Username = username
|
||||
log.Printf("NTLM: User %s authenticated successfully", username)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -34,6 +34,13 @@ type Configuration struct {
|
||||
Caps RDGCapsConfig `koanf:"caps"`
|
||||
Security SecurityConfig `koanf:"security"`
|
||||
Client ClientConfig `koanf:"client"`
|
||||
PXVDI PXVDIConfig `koanf:"pxvdi"`
|
||||
}
|
||||
|
||||
type PXVDIConfig struct {
|
||||
Enabled bool `koanf:"enabled"`
|
||||
ApiUrl string `koanf:"apiurl"`
|
||||
ApiKey string `koanf:"apikey"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
@ -184,6 +191,7 @@ func Load(configFile string) Configuration {
|
||||
k.UnmarshalWithConf("Security", &Conf.Security, koanfTag)
|
||||
k.UnmarshalWithConf("Client", &Conf.Client, koanfTag)
|
||||
k.UnmarshalWithConf("Kerberos", &Conf.Kerberos, koanfTag)
|
||||
k.UnmarshalWithConf("PXVDI", &Conf.PXVDI, koanfTag)
|
||||
|
||||
if len(Conf.Security.PAATokenEncryptionKey) != 32 {
|
||||
Conf.Security.PAATokenEncryptionKey, _ = security.GenerateRandomString(32)
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/bolkedebruin/gokrb5/v8/keytab"
|
||||
"github.com/bolkedebruin/gokrb5/v8/service"
|
||||
@ -23,6 +24,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -65,6 +67,27 @@ func initOIDC(callbackUrl *url.URL) *web.OIDC {
|
||||
return o.New()
|
||||
}
|
||||
|
||||
// 定时记录连接用户信息
|
||||
func startConnectionLogger(interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
connections := protocol.GetActiveConnections()
|
||||
if len(connections) > 0 {
|
||||
connData, err := json.Marshal(connections)
|
||||
if err != nil {
|
||||
log.Printf("连接信息序列化失败: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("当前活跃连接数: %d, 连接详情: %s", len(connections), string(connData))
|
||||
} else {
|
||||
log.Printf("当前无活跃连接")
|
||||
}
|
||||
}
|
||||
}()
|
||||
log.Printf("启动连接信息记录器,间隔时间: %v", interval)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// load config
|
||||
_, err := flags.Parse(&opts)
|
||||
@ -125,6 +148,10 @@ func main() {
|
||||
h := w.NewHandler()
|
||||
|
||||
log.Printf("Starting remote desktop gateway server")
|
||||
|
||||
// 启动连接信息记录器
|
||||
startConnectionLogger(10 * time.Second)
|
||||
|
||||
cfg := &tls.Config{}
|
||||
|
||||
// configure tls security
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package protocol
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Connections map[string]*Monitor
|
||||
|
||||
@ -41,6 +44,40 @@ func Disconnect(id string) error {
|
||||
return fmt.Errorf("%s connection does not exist", id)
|
||||
}
|
||||
|
||||
// GetActiveConnections 返回所有当前活跃的连接信息
|
||||
func GetActiveConnections() []map[string]interface{} {
|
||||
connections := []map[string]interface{}{}
|
||||
|
||||
for id, monitor := range Connections {
|
||||
tunnel := monitor.Tunnel
|
||||
if tunnel == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 计算连接持续时间
|
||||
duration := time.Since(tunnel.ConnectedOn)
|
||||
|
||||
// 收集每个连接的关键信息
|
||||
connInfo := map[string]interface{}{
|
||||
"id": id,
|
||||
"rdgId": tunnel.RDGId,
|
||||
"targetServer": tunnel.TargetServer,
|
||||
"remoteAddr": tunnel.RemoteAddr,
|
||||
"userName": tunnel.User.UserName(),
|
||||
"domain": tunnel.User.Domain(),
|
||||
"connectedOn": tunnel.ConnectedOn,
|
||||
"lastSeen": tunnel.LastSeen,
|
||||
"bytesSent": tunnel.BytesSent,
|
||||
"bytesReceived": tunnel.BytesReceived,
|
||||
"durationSecs": int(duration.Seconds()),
|
||||
}
|
||||
|
||||
connections = append(connections, connInfo)
|
||||
}
|
||||
|
||||
return connections
|
||||
}
|
||||
|
||||
// CalculateSpeedPerSecond calculate moving average.
|
||||
/*
|
||||
func CalculateSpeedPerSecond(connId string) (in int, out int) {
|
||||
|
||||
11
debian/changelog
vendored
Normal file
11
debian/changelog
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
rdpgw (2.0.3) UNRELEASED; urgency=medium
|
||||
|
||||
* add Dynamic Gateway
|
||||
|
||||
-- Lierfang Support Team <itsupport@lierfang.com> Fri, 13 Jun 2025 15:56:06 +0800
|
||||
|
||||
rdpgw (2.0.2) UNSTABLE; urgency=medium
|
||||
|
||||
* init
|
||||
|
||||
-- Jiangcuo <Jiangcuo@lierfang.com> Mon, 03 Feb 2025 13:56:49 +0800
|
||||
21
debian/control
vendored
Normal file
21
debian/control
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Source: rdpgw
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Lierfang <it_support@lierfang.com>
|
||||
Homepage: https://github.com/bolkedebruin/rdpgw
|
||||
Build-Depends: debhelper-compat (= 12),
|
||||
golang ( >= 1.23.5-1~bpo12+1 ),
|
||||
build-essential,
|
||||
libpam0g-dev,
|
||||
dh-golang
|
||||
|
||||
Package: rdpgw
|
||||
Architecture: any
|
||||
Depends: dbus,
|
||||
openssl,
|
||||
${misc:Depends},
|
||||
${shlibs:Depends},
|
||||
Description: rdpgw
|
||||
RDPGW is an implementation of the Remote Desktop Gateway protocol.
|
||||
This allows you to connect with the official Microsoft clients to remote desktops over HTTPS.
|
||||
These desktops could be, for example, XRDP desktops running in containers on Kubernetes.
|
||||
11
debian/copyright
vendored
Normal file
11
debian/copyright
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Source: https://github.com/bolkedebruin/rdpgw
|
||||
|
||||
|
||||
Files: *
|
||||
Copyright: bolkedebruin
|
||||
License: Apache-2.0
|
||||
|
||||
Files: debian/*
|
||||
Copyright: Lierfang <service@lierfang.com>
|
||||
License: Apache-2.0
|
||||
5
debian/install
vendored
Normal file
5
debian/install
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
bin/rdpgw usr/bin/
|
||||
bin/rdpgw-auth usr/sbin/
|
||||
debian/rdpgw.yaml etc/rdpgw/
|
||||
debian/rdpgw-auth.yaml etc/rdpgw/
|
||||
debian/rdpgw-auth.service lib/systemd/system/
|
||||
21
debian/postinst
vendored
Normal file
21
debian/postinst
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
if [ ! -f "/etc/rdpgw/server.pem" ]; then
|
||||
random=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
|
||||
openssl genrsa -des3 -passout pass:$random -out /tmp/server.pass.key 2048
|
||||
openssl rsa -passin pass:$random -in /tmp/server.pass.key -out /etc/rdpgw/key.pem
|
||||
rm /tmp/server.pass.key
|
||||
openssl req -new -sha256 -key /etc/rdpgw/key.pem -out /etc/rdpgw/server.csr -subj "/C=US/ST=VA/L=SomeCity/O=MyCompany/OU=MyDivision/CN=rdpgw"
|
||||
openssl x509 -req -days 365 -in /etc/rdpgw/server.csr -signkey /etc/rdpgw/key.pem -out /etc/rdpgw/server.pem
|
||||
fi
|
||||
deb-systemd-invoke reload-or-try-restart rdpgw-auth.service || true
|
||||
deb-systemd-invoke reload-or-try-restart rdpgw.service || true
|
||||
;;
|
||||
|
||||
esac
|
||||
16
debian/rdpgw-auth.service
vendored
Normal file
16
debian/rdpgw-auth.service
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=RDP Gateway Auth Service
|
||||
After=network.target
|
||||
StartLimitBurst=5
|
||||
StartLimitInterval=10s
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/sbin/rdpgw-auth -c /etc/rdpgw/rdpgw-auth.yaml -s /run/rdpgw-auth.sock
|
||||
Restart=on-failure
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
6
debian/rdpgw-auth.yaml
vendored
Normal file
6
debian/rdpgw-auth.yaml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
PXVDI:
|
||||
Enabled: true
|
||||
apiUrl: "https://10.13.16.164:3002"
|
||||
apiKey: "dasdasdasdas"
|
||||
Users:
|
||||
- {Username: "debian-rdpgw-start", Password: "debian-rdpgw-password"}
|
||||
16
debian/rdpgw.service
vendored
Normal file
16
debian/rdpgw.service
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=RDP Gateway Service
|
||||
After=network.target
|
||||
StartLimitBurst=5
|
||||
StartLimitInterval=10s
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/rdpgw -c /etc/rdpgw/rdpgw.yaml
|
||||
Restart=on-failure
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
30
debian/rdpgw.yaml
vendored
Normal file
30
debian/rdpgw.yaml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
Server:
|
||||
Authentication:
|
||||
- ntlm
|
||||
BasicAuthTimeout: 5
|
||||
AuthSocket: /run/rdpgw-auth.sock
|
||||
GatewayAddress: localhost
|
||||
Port: 443
|
||||
Hosts:
|
||||
- localhost:3389
|
||||
HostSelection: any
|
||||
Tls: enable
|
||||
CertFile: /etc/rdpgw/server.pem
|
||||
KeyFile: /etc/rdpgw/key.pem
|
||||
Caps:
|
||||
SmartCardAuth: false
|
||||
TokenAuth: false
|
||||
IdleTimeout: 10
|
||||
EnablePrinter: true
|
||||
EnablePort: true
|
||||
EnablePnp: true
|
||||
EnableDrive: true
|
||||
EnableClipboard: true
|
||||
Client:
|
||||
UsernameTemplate: "{{ username }}"
|
||||
SplitUserDomain: false
|
||||
Security:
|
||||
PAATokenSigningKey: thisisasessionkeyreplacethisjetzt
|
||||
UserTokenEncryptionKey: thisisasessionkeyreplacethisjetzt
|
||||
EnableUserToken: false
|
||||
VerifyClientIp: trufalsee
|
||||
19
debian/rules
vendored
Executable file
19
debian/rules
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
|
||||
override_dh_auto_test:
|
||||
|
||||
override_dh_auto_install:
|
||||
|
||||
override_dh_auto_clean:
|
||||
|
||||
override_dh_dwz:
|
||||
84
docs/api-auth.md
Normal file
84
docs/api-auth.md
Normal file
@ -0,0 +1,84 @@
|
||||
# NTLM认证API集成
|
||||
|
||||
RDPGW支持通过API集成NTLM认证,允许使用您自己的用户管理系统进行认证。
|
||||
|
||||
## API模式
|
||||
|
||||
API支持两种模式:
|
||||
|
||||
1. **验证模式(verify)**:验证用户凭据是否有效
|
||||
2. **密码获取模式(getpassword)**:获取用户的明文密码,用于NTLM挑战-响应计算
|
||||
|
||||
## API要求
|
||||
|
||||
### 1. 验证模式
|
||||
|
||||
用于验证用户凭据是否有效。
|
||||
|
||||
**请求格式**:
|
||||
```
|
||||
GET https://your-api-server/api/checkperm/?username=<用户名>&password=<密码>&mode=verify
|
||||
```
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 密码获取模式
|
||||
|
||||
用于获取用户的明文密码,这是NTLM认证所必需的。
|
||||
|
||||
**请求格式**:
|
||||
```
|
||||
GET https://your-api-server/api/checkperm/?username=<用户名>&mode=getpassword
|
||||
```
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"password": "用户的明文密码"
|
||||
}
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
在`rdpgw-auth.yaml`中配置API认证:
|
||||
|
||||
```yaml
|
||||
apiauth:
|
||||
enabled: true
|
||||
apiurl: "https://your-api-server/api/checkperm/"
|
||||
```
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **密码安全**:API必须通过HTTPS提供,并且在内部网络中运行以确保安全性
|
||||
|
||||
2. **密码存储**:您的API服务必须能够以某种形式获取或计算用户密码,这需要安全的密码存储机制
|
||||
|
||||
3. **替代方案**:如果不想暴露明文密码,请考虑使用其他认证方式,如OpenID Connect或基本认证
|
||||
|
||||
## NTLM认证流程
|
||||
|
||||
1. 客户端(如FreeRDP)发送NTLM协商消息到RDPGW
|
||||
2. RDPGW生成挑战并发送回客户端
|
||||
3. 客户端计算响应并发送认证消息
|
||||
4. RDPGW调用API以`getpassword`模式获取用户密码
|
||||
5. RDPGW使用获取的密码计算期望的响应并与客户端响应比较
|
||||
6. 如果匹配,认证成功;否则,认证失败
|
||||
|
||||
## 优势
|
||||
|
||||
- 保持标准NTLM认证流程,客户端无需修改
|
||||
- 与您的用户管理系统集成
|
||||
- 支持所有NTLM客户端,包括标准Windows远程桌面客户端
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 您的API必须能够安全地存储和提供用户密码
|
||||
- 明文密码传输存在固有的安全风险,即使在加密通道中也是如此
|
||||
- 确保API服务器仅对RDPGW服务器可访问,并考虑实施IP限制或类似措施
|
||||
23
docs/rdpgw-auth-api.yaml.example
Normal file
23
docs/rdpgw-auth-api.yaml.example
Normal file
@ -0,0 +1,23 @@
|
||||
# RDPGW认证配置示例
|
||||
# 将此文件保存为rdpgw-auth.yaml并根据您的环境调整设置
|
||||
|
||||
# API认证配置
|
||||
ApiAuth:
|
||||
# 启用API认证
|
||||
enabled: true
|
||||
# API URL - 验证用户凭据的端点
|
||||
# NTLM认证需要API提供明文密码
|
||||
# 此API需要支持以下两种模式:
|
||||
# 1. ?username=user&mode=getpassword - 返回用户的明文密码
|
||||
# 2. ?username=user&password=pass&mode=verify - 验证凭据是否正确
|
||||
apiurl: "https://your-api-server/api/checkperm/"
|
||||
|
||||
# 用户配置
|
||||
# 当使用API认证时,此部分可以为空
|
||||
# 如果为特定用户提供密码,将绕过API并使用本地密码
|
||||
Users: []
|
||||
|
||||
# 或者,您也可以同时支持API认证和本地用户认证
|
||||
# 在这种情况下,请提供本地用户凭据
|
||||
# users:
|
||||
# - {Username: "local_user", Password: "local_password"}
|
||||
132
index.js
Normal file
132
index.js
Normal file
@ -0,0 +1,132 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 配置信息
|
||||
const config = {
|
||||
port: 3000,
|
||||
// 用户信息,可以通过配置文件覆盖
|
||||
users: {
|
||||
'testuser': 'testpassword',
|
||||
'admin': 'adminpass',
|
||||
'user1': 'password1'
|
||||
},
|
||||
// API路径
|
||||
apiPath: '/api/checkperm'
|
||||
};
|
||||
|
||||
// 尝试加载配置文件
|
||||
try {
|
||||
const configPath = path.join(__dirname, 'config.json');
|
||||
if (fs.existsSync(configPath)) {
|
||||
const fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
// 合并配置
|
||||
Object.assign(config, fileConfig);
|
||||
console.log('已加载配置文件');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('加载配置文件失败,使用默认配置:', error.message);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
// 添加中间件解析JSON和表单数据
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 处理认证逻辑的函数
|
||||
function handleAuth(username, password, mode) {
|
||||
// 验证参数是否存在
|
||||
if (!username) {
|
||||
console.log('缺少必要参数: username');
|
||||
return { status: 400, response: { status: 'error', message: '缺少必要参数: username' } };
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
if (!config.users[username]) {
|
||||
console.log(`用户不存在: ${username}`);
|
||||
return { status: 401, response: { status: 'error', message: '用户不存在' } };
|
||||
}
|
||||
|
||||
// 根据模式处理请求
|
||||
if (mode === 'getpassword') {
|
||||
// 密码获取模式 - 用于NTLM认证
|
||||
console.log(`返回用户 ${username} 的密码`);
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
status: 'success',
|
||||
password: config.users[username]
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// 默认为verify模式 - 验证用户名和密码
|
||||
if (!password) {
|
||||
console.log('缺少必要参数: password');
|
||||
return { status: 400, response: { status: 'error', message: '缺少必要参数: password' } };
|
||||
}
|
||||
|
||||
if (config.users[username] === password) {
|
||||
console.log('认证成功');
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
status: 'success',
|
||||
user: username
|
||||
}
|
||||
};
|
||||
} else {
|
||||
console.log('认证失败: 密码不正确');
|
||||
return { status: 401, response: { status: 'error', message: '认证失败' } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 认证API端点 - GET
|
||||
app.get(config.apiPath, (req, res) => {
|
||||
const { username, password, mode } = req.query;
|
||||
|
||||
console.log('收到GET认证请求:');
|
||||
console.log(`username: ${username}`);
|
||||
console.log(`mode: ${mode || 'verify'}`);
|
||||
if (password) {
|
||||
console.log(`password: ${'*'.repeat(password ? password.length : 0)}`); // 为安全起见不打印实际密码
|
||||
}
|
||||
|
||||
const result = handleAuth(username, password, mode);
|
||||
return res.status(result.status).json(result.response);
|
||||
});
|
||||
|
||||
// 认证API端点 - POST
|
||||
app.post(config.apiPath, (req, res) => {
|
||||
const { username, password, mode } = req.body;
|
||||
|
||||
console.log('收到POST认证请求:');
|
||||
console.log(`username: ${username}`);
|
||||
console.log(`mode: ${mode || 'verify'}`);
|
||||
if (password) {
|
||||
console.log(`password: ${'*'.repeat(password ? password.length : 0)}`); // 为安全起见不打印实际密码
|
||||
}
|
||||
|
||||
const result = handleAuth(username, password, mode);
|
||||
return res.status(result.status).json(result.response);
|
||||
});
|
||||
|
||||
// 根路径返回服务信息
|
||||
app.get('/', (req, res) => {
|
||||
res.send('RDPGW远程认证测试服务已启动');
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
app.listen(config.port, () => {
|
||||
console.log(`认证服务器已启动,监听端口: ${config.port}`);
|
||||
console.log('当前配置:');
|
||||
console.log(`- 端口: ${config.port}`);
|
||||
console.log(`- API路径: ${config.apiPath}`);
|
||||
console.log(`- 已配置用户数: ${Object.keys(config.users).length}`);
|
||||
console.log('\n支持的模式:');
|
||||
console.log(`1. 验证模式 (GET): http://localhost:${config.port}${config.apiPath}?username=testuser&password=testpassword&mode=verify`);
|
||||
console.log(`2. 密码获取模式 (GET): http://localhost:${config.port}${config.apiPath}?username=testuser&mode=getpassword`);
|
||||
console.log('---');
|
||||
console.log('POST请求也支持,可以通过请求体发送参数');
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user