1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
using System.Security.Cryptography;
using System.Text;
using CrupestApi.Commons;
using Dapper;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Options;
namespace CrupestApi.Secrets;
public class SecretsService : ISecretsService
{
private readonly IOptionsSnapshot<CrupestApiConfig> _crupestApiConfig;
private readonly ILogger<SecretsService> _logger;
public SecretsService(IOptionsSnapshot<CrupestApiConfig> crupestApiConfig, ILogger<SecretsService> logger)
{
_crupestApiConfig = crupestApiConfig;
_logger = logger;
}
private string GetDatabasePath()
{
return Path.Combine(_crupestApiConfig.Value.DataDir, "secrets.db");
}
private async Task<SqliteConnection> EnsureDatabase()
{
var dataSource = GetDatabasePath();
var connectionStringBuilder = new SqliteConnectionStringBuilder()
{
DataSource = dataSource
};
if (!File.Exists(dataSource))
{
_logger.LogInformation("Data source {0} does not exist. Create one.", dataSource);
connectionStringBuilder.Mode = SqliteOpenMode.ReadWriteCreate;
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
var transaction = await connection.BeginTransactionAsync();
connection.Execute(@"
CREATE TABLE secrets (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Key TEXT NOT NULL,
Secret TEXT NOT NULL,
Description TEXT NOT NULL,
ExpireTime TEXT,
Revoked INTEGER NOT NULL,
CreateTime TEXT NOT NULL
);
CREATE INDEX secrets_key ON secrets (key);
INSERT INTO secrets (Key, Secret, Description, ExpireTime, Revoked, CreateTime) VALUES (@SecretManagementKey, 'crupest', 'This is the default secret management key.', NULL, 0, @CreateTime);
",
new
{
SecretManagementKey = SecretsConstants.SecretManagementKey,
CreateTime = DateTime.Now.ToString("O"),
});
await transaction.CommitAsync();
_logger.LogInformation("{0} created with 'crupest' as the default secret management value. Please immediate revoke it and create a new one.", dataSource);
return connection;
}
else
{
_logger.LogInformation("Data source {0} already exists. Will use it.");
connectionStringBuilder.Mode = SqliteOpenMode.ReadWrite;
var connectionString = connectionStringBuilder.ToString();
return new SqliteConnection(connectionString);
}
}
private string GenerateRandomKey(int length)
{
const string alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
using var randomNumberGenerator = RandomNumberGenerator.Create();
var result = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
result.Append(alphanum[i]);
}
return result.ToString();
}
public async Task<SecretInfo> CreateSecretAsync(string key, string description, DateTime? expireTime = null)
{
var dbConnection = await EnsureDatabase();
var secret = GenerateRandomKey(16);
var now = DateTime.Now;
dbConnection.Execute(@"
INSERT INTO secrets (Key, Secret, Description, ExpireTime, Revoked, CreateTime) VALUES (@Key, @Secret, @Description, @ExpireTime, 0, @CreateTime);
",
new
{
Key = key,
Secret = secret,
Description = description,
ExpireTime = expireTime?.ToString("O"),
CreateTime = now.ToString("O"),
});
return new SecretInfo(key, secret, description, expireTime, false, now);
}
public Task<List<SecretInfo>> GetSecretListAsync(bool includeExpired = false, bool includeRevoked = false)
{
throw new NotImplementedException();
}
public Task<List<SecretInfo>> GetSecretListByKeyAsync(string key, bool includeExpired = false, bool includeRevoked = false)
{
throw new NotImplementedException();
}
public Task<SecretInfo> ModifySecretAsync(string secret, SecretModifyRequest modifyRequest)
{
throw new NotImplementedException();
}
public Task RevokeSecretAsync(string secret)
{
throw new NotImplementedException();
}
public Task<bool> VerifySecretAsync(string key, string secret)
{
throw new NotImplementedException();
}
public Task VerifySecretForHttpRequestAsync(HttpRequest request, string key, string queryKey = "secret")
{
throw new NotImplementedException();
}
}
|