parent
114a7c3dab
commit
103d765735
|
@ -0,0 +1,3 @@
|
|||
tmp
|
||||
logs
|
||||
public/static/dist
|
|
@ -1,4 +1,4 @@
|
|||
name: build
|
||||
name: build and push binary to release
|
||||
|
||||
on:
|
||||
release:
|
||||
|
@ -16,6 +16,8 @@ jobs:
|
|||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||
- uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }} # 一个默认的变量,用来实现往 Release 中添加文件
|
||||
|
@ -23,4 +25,4 @@ jobs:
|
|||
goarch: ${{ matrix.goarch }}
|
||||
goversion: 1.18 # 可以指定编译使用的 Golang 版本
|
||||
binary_name: "go-ldap-admin" # 可以指定二进制文件的名称
|
||||
extra_files: LICENSE config.yml go-ldap-admin-priv.pem go-ldap-admin-pub.pem rbac_model.conf README.md # 需要包含的额外文件
|
||||
extra_files: LICENSE config.yml README.md # 需要包含的额外文件
|
|
@ -11,16 +11,20 @@ jobs:
|
|||
with:
|
||||
go-version: 1.18
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.47.3
|
||||
version: v1.57.2
|
||||
args: --timeout=5m --skip-files="public/client/feishu/feishu.go"
|
||||
build:
|
||||
name: go-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
|
@ -23,4 +23,5 @@ go-ldap-admin.db
|
|||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
tmp
|
||||
docs/docker-compose/data
|
||||
docs/docker-compose/data
|
||||
dist
|
40
Dockerfile
40
Dockerfile
|
@ -1,39 +1,29 @@
|
|||
FROM golang:1.18.10-alpine3.16 AS builder
|
||||
|
||||
# ENV GOPROXY https://goproxy.io
|
||||
|
||||
RUN mkdir /app && apk add --no-cache --virtual .build-deps \
|
||||
ca-certificates \
|
||||
gcc \
|
||||
g++
|
||||
|
||||
ADD . /app/
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/ali_eryajf/golang:1.18.10-alpine3.17 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories \
|
||||
&& apk upgrade && apk add --no-cache --virtual .build-deps \
|
||||
ca-certificates gcc g++ curl
|
||||
|
||||
ADD . .
|
||||
|
||||
RUN release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||
|
||||
RUN sed -i 's@localhost:389@openldap:389@g' /app/config.yml \
|
||||
&& sed -i 's@host: localhost@host: mysql@g' /app/config.yml && go build -o go-ldap-admin .
|
||||
|
||||
### build final image
|
||||
FROM alpine:3.16
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/ali_eryajf/alpine:3.19
|
||||
|
||||
# we set the timezone `Asia/Shanghai` by default, you can be modified
|
||||
# by `docker build --build-arg="TZ=Other_Timezone ..."`
|
||||
ARG TZ="Asia/Shanghai"
|
||||
|
||||
ENV TZ ${TZ}
|
||||
|
||||
RUN mkdir /app
|
||||
LABEL maintainer eryajf@163.com
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/ .
|
||||
|
||||
|
||||
RUN apk upgrade \
|
||||
&& apk add bash tzdata sqlite vim \
|
||||
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
|
||||
&& echo ${TZ} > /etc/timezone
|
||||
COPY --from=builder /app/wait .
|
||||
COPY --from=builder /app/LICENSE .
|
||||
COPY --from=builder /app/config.yml .
|
||||
COPY --from=builder /app/go-ldap-admin .
|
||||
|
||||
RUN chmod +x wait go-ldap-admin
|
||||
|
||||
|
|
|
@ -8,10 +8,6 @@ system:
|
|||
port: 8888
|
||||
# 是否初始化数据(没有初始数据时使用, 已发布正式版改为false)
|
||||
init-data: true
|
||||
# rsa公钥文件路径(config.yml相对路径, 也可以填绝对路径)
|
||||
rsa-public-key: go-ldap-admin-pub.pem
|
||||
# rsa私钥文件路径(config.yml相对路径, 也可以填绝对路径)
|
||||
rsa-private-key: go-ldap-admin-priv.pem
|
||||
|
||||
logs:
|
||||
# 日志等级(-1:Debug, 0:Info, 1:Warn, 2:Error, 3:DPanic, 4:Panic, 5:Fatal, -1<=level<=5, 参照zap.level源码)
|
||||
|
@ -55,11 +51,6 @@ mysql:
|
|||
# 字符集(utf8mb4_general_ci速度比utf8mb4_unicode_ci快些)
|
||||
collation: utf8mb4_general_ci
|
||||
|
||||
# casbin配置
|
||||
casbin:
|
||||
# 模型配置文件, config.yml相对路径
|
||||
model-path: 'rbac_model.conf'
|
||||
|
||||
# jwt配置
|
||||
jwt:
|
||||
# jwt标识
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
|
@ -15,12 +16,18 @@ import (
|
|||
// 全局配置变量
|
||||
var Conf = new(config)
|
||||
|
||||
//go:embed go-ldap-admin-priv.pem
|
||||
var priv []byte
|
||||
|
||||
//go:embed go-ldap-admin-pub.pem
|
||||
var pub []byte
|
||||
|
||||
type config struct {
|
||||
System *SystemConfig `mapstructure:"system" json:"system"`
|
||||
Logs *LogsConfig `mapstructure:"logs" json:"logs"`
|
||||
Database *Database `mapstructure:"database" json:"database"`
|
||||
Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"`
|
||||
Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"`
|
||||
System *SystemConfig `mapstructure:"system" json:"system"`
|
||||
Logs *LogsConfig `mapstructure:"logs" json:"logs"`
|
||||
Database *Database `mapstructure:"database" json:"database"`
|
||||
Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"`
|
||||
// Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"`
|
||||
Jwt *JwtConfig `mapstructure:"jwt" json:"jwt"`
|
||||
RateLimit *RateLimitConfig `mapstructure:"rate-limit" json:"rateLimit"`
|
||||
Ldap *LdapConfig `mapstructure:"ldap" json:"ldap"`
|
||||
|
@ -50,8 +57,8 @@ func InitConfig() {
|
|||
panic(fmt.Errorf("初始化配置文件失败:%s", err))
|
||||
}
|
||||
// 读取rsa key
|
||||
Conf.System.RSAPublicBytes = RSAReadKeyFromFile(Conf.System.RSAPublicKey)
|
||||
Conf.System.RSAPrivateBytes = RSAReadKeyFromFile(Conf.System.RSAPrivateKey)
|
||||
Conf.System.RSAPublicBytes = pub
|
||||
Conf.System.RSAPrivateBytes = priv
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
@ -62,36 +69,16 @@ func InitConfig() {
|
|||
panic(fmt.Errorf("初始化配置文件失败:%s", err))
|
||||
}
|
||||
// 读取rsa key
|
||||
Conf.System.RSAPublicBytes = RSAReadKeyFromFile(Conf.System.RSAPublicKey)
|
||||
Conf.System.RSAPrivateBytes = RSAReadKeyFromFile(Conf.System.RSAPrivateKey)
|
||||
Conf.System.RSAPublicBytes = pub
|
||||
Conf.System.RSAPrivateBytes = priv
|
||||
|
||||
}
|
||||
|
||||
// 从文件中读取RSA key
|
||||
func RSAReadKeyFromFile(filename string) []byte {
|
||||
f, err := os.Open(filename)
|
||||
var b []byte
|
||||
|
||||
if err != nil {
|
||||
return b
|
||||
}
|
||||
defer f.Close()
|
||||
fileInfo, _ := f.Stat()
|
||||
b = make([]byte, fileInfo.Size())
|
||||
_, err = f.Read(b)
|
||||
if err != nil {
|
||||
return b
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type SystemConfig struct {
|
||||
Mode string `mapstructure:"mode" json:"mode"`
|
||||
UrlPathPrefix string `mapstructure:"url-path-prefix" json:"urlPathPrefix"`
|
||||
Port int `mapstructure:"port" json:"port"`
|
||||
InitData bool `mapstructure:"init-data" json:"initData"`
|
||||
RSAPublicKey string `mapstructure:"rsa-public-key" json:"rsaPublicKey"`
|
||||
RSAPrivateKey string `mapstructure:"rsa-private-key" json:"rsaPrivateKey"`
|
||||
RSAPublicBytes []byte `mapstructure:"-" json:"-"`
|
||||
RSAPrivateBytes []byte `mapstructure:"-" json:"-"`
|
||||
}
|
||||
|
@ -123,9 +110,9 @@ type MysqlConfig struct {
|
|||
Collation string `mapstructure:"collation" json:"collation"`
|
||||
}
|
||||
|
||||
type CasbinConfig struct {
|
||||
ModelPath string `mapstructure:"model-path" json:"modelPath"`
|
||||
}
|
||||
// type CasbinConfig struct {
|
||||
// ModelPath string `mapstructure:"model-path" json:"modelPath"`
|
||||
// }
|
||||
|
||||
type JwtConfig struct {
|
||||
Realm string `mapstructure:"realm" json:"realm"`
|
||||
|
|
|
@ -72,7 +72,6 @@ services:
|
|||
hostname: go-ldap-admin-server
|
||||
restart: always
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
WAIT_HOSTS: mysql:3306, openldap:389
|
||||
ports:
|
||||
- 8888:8888
|
||||
|
@ -86,19 +85,3 @@ services:
|
|||
- openldap:go-ldap-admin-openldap # ldap容器的 service_name:container_name
|
||||
networks:
|
||||
- go-ldap-admin
|
||||
|
||||
go-ldap-admin-ui:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/ali_eryajf/go-ldap-admin-ui
|
||||
container_name: go-ldap-admin-ui
|
||||
hostname: go-ldap-admin-ui
|
||||
restart: always
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- 8090:80
|
||||
depends_on:
|
||||
- go-ldap-admin-server
|
||||
links:
|
||||
- go-ldap-admin-server:go-ldap-admin-server
|
||||
networks:
|
||||
- go-ldap-admin
|
2
main.go
2
main.go
|
@ -68,7 +68,7 @@ func main() {
|
|||
// 启动定时任务
|
||||
logic.InitCron()
|
||||
|
||||
common.Log.Info(fmt.Sprintf("Server is running at %s:%d/%s", host, port, config.Conf.System.UrlPathPrefix))
|
||||
common.Log.Info(fmt.Sprintf("Server is running at http://%s:%d", host, port))
|
||||
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 5 seconds.
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const INDEX = "index.html"
|
||||
|
||||
type ServeFileSystem interface {
|
||||
http.FileSystem
|
||||
Exists(prefix string, path string) bool
|
||||
}
|
||||
|
||||
type localFileSystem struct {
|
||||
http.FileSystem
|
||||
root string
|
||||
indexes bool
|
||||
}
|
||||
|
||||
func LocalFile(root string, indexes bool) *localFileSystem {
|
||||
return &localFileSystem{
|
||||
FileSystem: gin.Dir(root, indexes),
|
||||
root: root,
|
||||
indexes: indexes,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *localFileSystem) Exists(prefix string, filepath string) bool {
|
||||
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
|
||||
name := path.Join(l.root, p)
|
||||
stats, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if stats.IsDir() {
|
||||
if !l.indexes {
|
||||
index := path.Join(name, INDEX)
|
||||
_, err := os.Stat(index)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
|
||||
return Serve(urlPrefix, LocalFile(root, false))
|
||||
}
|
||||
|
||||
// Static returns a middleware handler that serves static files in the given directory.
|
||||
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
|
||||
fileserver := http.FileServer(fs)
|
||||
if urlPrefix != "" {
|
||||
fileserver = http.StripPrefix(urlPrefix, fileserver)
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
if fs.Exists(urlPrefix, c.Request.URL.Path) {
|
||||
fileserver.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type embedFileSystem struct {
|
||||
http.FileSystem
|
||||
}
|
||||
|
||||
func (e embedFileSystem) Exists(prefix string, path string) bool {
|
||||
_, err := e.Open(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func EmbedFolder(fsEmbed embed.FS, targetPath string) ServeFileSystem {
|
||||
fsys, err := fs.Sub(fsEmbed, targetPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return embedFileSystem{
|
||||
FileSystem: http.FS(fsys),
|
||||
}
|
||||
}
|
|
@ -3,9 +3,8 @@ package common
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/eryajf/go-ldap-admin/config"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
gormadapter "github.com/casbin/gorm-adapter/v3"
|
||||
)
|
||||
|
||||
|
@ -24,12 +23,33 @@ func InitCasbinEnforcer() {
|
|||
Log.Info("初始化Casbin完成!")
|
||||
}
|
||||
|
||||
var casbinModel = `
|
||||
[request_definition]
|
||||
r = sub, obj, act
|
||||
|
||||
[policy_definition]
|
||||
p = sub, obj, act
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow))
|
||||
|
||||
[matchers]
|
||||
m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
|
||||
`
|
||||
|
||||
func mysqlCasbin() (*casbin.Enforcer, error) {
|
||||
a, err := gormadapter.NewAdapterByDB(DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e, err := casbin.NewEnforcer(config.Conf.Casbin.ModelPath, a)
|
||||
m, err := model.NewModelFromString(casbinModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e, err := casbin.NewEnforcer(m, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package static
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed all:dist
|
||||
var Static embed.FS
|
|
@ -1,14 +0,0 @@
|
|||
[request_definition]
|
||||
r = sub, obj, act
|
||||
|
||||
[policy_definition]
|
||||
p = sub, obj, act
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow))
|
||||
|
||||
[matchers]
|
||||
m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
|
|
@ -2,11 +2,13 @@ package routes
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/eryajf/go-ldap-admin/config"
|
||||
"github.com/eryajf/go-ldap-admin/middleware"
|
||||
"github.com/eryajf/go-ldap-admin/public/common"
|
||||
"github.com/eryajf/go-ldap-admin/public/static"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
@ -23,6 +25,16 @@ func InitRoutes() *gin.Engine {
|
|||
// r := gin.New()
|
||||
// r.Use(gin.Recovery())
|
||||
|
||||
r.Use(middleware.Serve("/", middleware.EmbedFolder(static.Static, "dist")))
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
data, err := static.Static.ReadFile("dist/index.html")
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
|
||||
})
|
||||
|
||||
// 启用限流中间件
|
||||
// 默认每50毫秒填充一个令牌,最多填充200个
|
||||
fillInterval := time.Duration(config.Conf.RateLimit.FillInterval)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
> 因为一些测试场景需要依赖配置的初始化,因此这里单独一个目录放类似的测试
|
|
@ -1 +0,0 @@
|
|||
../config.yml
|
|
@ -1 +0,0 @@
|
|||
../go-ldap-admin-priv.pem
|
|
@ -1 +0,0 @@
|
|||
../go-ldap-admin-pub.pem
|
|
@ -1,46 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/eryajf/go-ldap-admin/config"
|
||||
"github.com/eryajf/go-ldap-admin/public/common"
|
||||
"github.com/eryajf/go-ldap-admin/public/tools"
|
||||
"github.com/eryajf/go-ldap-admin/service/isql"
|
||||
)
|
||||
|
||||
func InitConfig() {
|
||||
// 加载配置文件到全局配置结构体
|
||||
config.InitConfig()
|
||||
|
||||
// 初始化日志
|
||||
common.InitLogger()
|
||||
|
||||
// 初始化数据库(mysql)
|
||||
common.InitDB()
|
||||
|
||||
// 初始化ldap连接
|
||||
common.InitLDAP()
|
||||
|
||||
// 初始化casbin策略管理器
|
||||
common.InitCasbinEnforcer()
|
||||
|
||||
// 初始化Validator数据校验
|
||||
common.InitValidate()
|
||||
}
|
||||
|
||||
func TestUserExist(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
var u isql.UserService
|
||||
filter := tools.H{
|
||||
"id": "111",
|
||||
}
|
||||
|
||||
if u.Exist(filter) {
|
||||
fmt.Println("用户名已存在")
|
||||
} else {
|
||||
fmt.Println("用户名不存在")
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
../rbac_model.conf
|
|
@ -1,21 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/eryajf/go-ldap-admin/config"
|
||||
"github.com/eryajf/go-ldap-admin/public/tools"
|
||||
)
|
||||
|
||||
func TestUnGenPassword(t *testing.T) {
|
||||
InitConfig()
|
||||
pass := "$2a$10$FlzrnJeE3Ad8uokvSAl/gunkRZsdREwlFZZqPcwfkekXOc9oAa9KS"
|
||||
fmt.Printf("秘钥为:%s\n", config.Conf.System.RSAPrivateBytes)
|
||||
// 密码通过RSA解密
|
||||
decodeData, err := tools.RSADecrypt([]byte(pass), config.Conf.System.RSAPrivateBytes)
|
||||
if err != nil {
|
||||
fmt.Printf("密码解密失败:%s\n", err)
|
||||
}
|
||||
fmt.Printf("密码解密后为:%s\n", string(decodeData))
|
||||
}
|
Loading…
Reference in New Issue