pylego 0.1.31.1__py3-none-any.whl → 0.1.33__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pylego/go.mod +0 -21
- pylego/go.sum +40 -491
- pylego/lego.go +230 -27
- pylego/lego.so +0 -0
- pylego/pylego.py +112 -16
- {pylego-0.1.31.1.dist-info → pylego-0.1.33.dist-info}/METADATA +24 -1
- pylego-0.1.33.dist-info/RECORD +11 -0
- {pylego-0.1.31.1.dist-info → pylego-0.1.33.dist-info}/WHEEL +1 -1
- pylego-0.1.31.1.dist-info/RECORD +0 -11
- {pylego-0.1.31.1.dist-info → pylego-0.1.33.dist-info}/licenses/LICENSE +0 -0
- {pylego-0.1.31.1.dist-info → pylego-0.1.33.dist-info}/top_level.txt +0 -0
pylego/lego.go
CHANGED
|
@@ -11,8 +11,12 @@ import (
|
|
|
11
11
|
"encoding/pem"
|
|
12
12
|
"errors"
|
|
13
13
|
"fmt"
|
|
14
|
+
"net"
|
|
14
15
|
"os"
|
|
16
|
+
"strings"
|
|
17
|
+
"time"
|
|
15
18
|
|
|
19
|
+
"github.com/go-acme/lego/v4/acme"
|
|
16
20
|
"github.com/go-acme/lego/v4/certcrypto"
|
|
17
21
|
"github.com/go-acme/lego/v4/certificate"
|
|
18
22
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
|
@@ -23,13 +27,30 @@ import (
|
|
|
23
27
|
"github.com/go-acme/lego/v4/registration"
|
|
24
28
|
)
|
|
25
29
|
|
|
30
|
+
// Error code constants
|
|
31
|
+
const (
|
|
32
|
+
ErrInvalidArguments = "invalid_arguments"
|
|
33
|
+
ErrInvalidEnvironment = "invalid_environment"
|
|
34
|
+
ErrCertificateRequestFailed = "certificate_request_failed"
|
|
35
|
+
ErrInvalidPrivateKey = "invalid_private_key"
|
|
36
|
+
ErrKeyGenerationFailed = "key_generation_failed"
|
|
37
|
+
ErrLegoClientCreationFailed = "lego_client_creation_failed"
|
|
38
|
+
ErrDNSProviderFailed = "dns_provider_failed"
|
|
39
|
+
ErrAccountRegistrationFailed = "account_registration_failed"
|
|
40
|
+
ErrInvalidCSR = "invalid_csr"
|
|
41
|
+
ErrCertificateObtainFailed = "certificate_obtain_failed"
|
|
42
|
+
ErrNetworkError = "network_error"
|
|
43
|
+
ErrMarshalingFailed = "marshaling_failed"
|
|
44
|
+
)
|
|
45
|
+
|
|
26
46
|
type LegoInputArgs struct {
|
|
27
|
-
Email
|
|
28
|
-
PrivateKey
|
|
29
|
-
Server
|
|
30
|
-
CSR
|
|
31
|
-
Plugin
|
|
32
|
-
Env
|
|
47
|
+
Email string `json:"email"`
|
|
48
|
+
PrivateKey string `json:"private_key,omitempty"`
|
|
49
|
+
Server string `json:"server"`
|
|
50
|
+
CSR string `json:"csr"`
|
|
51
|
+
Plugin string `json:"plugin"`
|
|
52
|
+
Env map[string]string
|
|
53
|
+
DNSPropagationWait int `json:"dns_propagation_wait,omitempty"`
|
|
33
54
|
}
|
|
34
55
|
|
|
35
56
|
type LegoOutputResponse struct {
|
|
@@ -46,42 +67,217 @@ type Metadata struct {
|
|
|
46
67
|
Domain string `json:"domain"`
|
|
47
68
|
}
|
|
48
69
|
|
|
70
|
+
type Subproblem struct {
|
|
71
|
+
Type string `json:"type"` // Error type URN
|
|
72
|
+
Detail string `json:"detail"` // Human-readable message
|
|
73
|
+
Identifier Identifier `json:"identifier,omitempty"` // The identifier that caused this subproblem
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type Identifier struct {
|
|
77
|
+
Type string `json:"type"` // "dns" or "ip"
|
|
78
|
+
Value string `json:"value"` // Domain name or IP address
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type ErrorResponse struct {
|
|
82
|
+
Type string `json:"type"` // "acme" for CA server errors, "lego" for everything else
|
|
83
|
+
Code string `json:"code"` // Error code or category
|
|
84
|
+
Status *int `json:"status,omitempty"` // HTTP status if applicable (ACME errors)
|
|
85
|
+
Detail string `json:"detail"` // Human-readable message
|
|
86
|
+
ACMEType string `json:"acme_type,omitempty"` // Full ACME URN if applicable
|
|
87
|
+
Subproblems []Subproblem `json:"subproblems,omitempty"` // Detailed subproblems from ACME errors
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type LegoResponse struct {
|
|
91
|
+
Success bool `json:"success"`
|
|
92
|
+
Error *ErrorResponse `json:"error,omitempty"`
|
|
93
|
+
Data *LegoOutputResponse `json:"data,omitempty"`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func isNetworkError(err error) bool {
|
|
97
|
+
if err == nil {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
var (
|
|
101
|
+
netErr net.Error
|
|
102
|
+
dnsErr *net.DNSError
|
|
103
|
+
opErr *net.OpError
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
switch {
|
|
107
|
+
case errors.As(err, &netErr):
|
|
108
|
+
return true
|
|
109
|
+
case errors.As(err, &dnsErr):
|
|
110
|
+
return true
|
|
111
|
+
case errors.As(err, &opErr):
|
|
112
|
+
return true
|
|
113
|
+
default:
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func extractSubproblems(problemDetails *acme.ProblemDetails) []Subproblem {
|
|
119
|
+
var subproblems []Subproblem
|
|
120
|
+
for _, sub := range problemDetails.SubProblems {
|
|
121
|
+
subCode := "unknown"
|
|
122
|
+
if sub.Type != "" {
|
|
123
|
+
parts := strings.Split(sub.Type, ":")
|
|
124
|
+
if len(parts) > 0 {
|
|
125
|
+
subCode = parts[len(parts)-1]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
subproblems = append(subproblems, Subproblem{
|
|
129
|
+
Type: subCode,
|
|
130
|
+
Detail: sub.Detail,
|
|
131
|
+
Identifier: Identifier{
|
|
132
|
+
Type: sub.Identifier.Type,
|
|
133
|
+
Value: sub.Identifier.Value,
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
return subproblems
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func wrapError(err error, context string) *ErrorResponse {
|
|
141
|
+
if err == nil {
|
|
142
|
+
return nil
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
var ctxErr *contextError
|
|
146
|
+
if errors.As(err, &ctxErr) {
|
|
147
|
+
context = ctxErr.context
|
|
148
|
+
err = ctxErr.original
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var problemDetails *acme.ProblemDetails
|
|
152
|
+
if errors.As(err, &problemDetails) {
|
|
153
|
+
code := "unknown"
|
|
154
|
+
if problemDetails.Type != "" {
|
|
155
|
+
parts := strings.Split(problemDetails.Type, ":")
|
|
156
|
+
if len(parts) > 0 {
|
|
157
|
+
code = parts[len(parts)-1]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
status := problemDetails.HTTPStatus
|
|
161
|
+
|
|
162
|
+
return &ErrorResponse{
|
|
163
|
+
Type: "acme",
|
|
164
|
+
Code: code,
|
|
165
|
+
Status: &status,
|
|
166
|
+
Detail: problemDetails.Detail,
|
|
167
|
+
ACMEType: problemDetails.Type,
|
|
168
|
+
Subproblems: extractSubproblems(problemDetails),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if isNetworkError(err) {
|
|
173
|
+
return &ErrorResponse{
|
|
174
|
+
Type: "lego",
|
|
175
|
+
Code: ErrNetworkError,
|
|
176
|
+
Detail: err.Error(),
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return &ErrorResponse{
|
|
181
|
+
Type: "lego",
|
|
182
|
+
Code: context,
|
|
183
|
+
Detail: err.Error(),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func buildErrorResponse(err error, context string) *C.char {
|
|
188
|
+
response := LegoResponse{
|
|
189
|
+
Success: false,
|
|
190
|
+
Error: wrapError(err, context),
|
|
191
|
+
}
|
|
192
|
+
responseJSON, marshalErr := json.Marshal(response)
|
|
193
|
+
if marshalErr == nil {
|
|
194
|
+
return C.CString(string(responseJSON))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fallbackResponse := LegoResponse{
|
|
198
|
+
Success: false,
|
|
199
|
+
Error: &ErrorResponse{
|
|
200
|
+
Type: "lego",
|
|
201
|
+
Code: ErrMarshalingFailed,
|
|
202
|
+
Detail: marshalErr.Error(),
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
fallbackJSON, _ := json.Marshal(fallbackResponse)
|
|
206
|
+
|
|
207
|
+
return C.CString(string(fallbackJSON))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
func buildSuccessResponse(data *LegoOutputResponse) *C.char {
|
|
211
|
+
response := LegoResponse{
|
|
212
|
+
Success: true,
|
|
213
|
+
Data: data,
|
|
214
|
+
}
|
|
215
|
+
responseJSON, marshalErr := json.Marshal(response)
|
|
216
|
+
if marshalErr == nil {
|
|
217
|
+
return C.CString(string(responseJSON))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fallbackResponse := LegoResponse{
|
|
221
|
+
Success: false,
|
|
222
|
+
Error: &ErrorResponse{
|
|
223
|
+
Type: "lego",
|
|
224
|
+
Code: ErrMarshalingFailed,
|
|
225
|
+
Detail: marshalErr.Error(),
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
fallbackJSON, _ := json.Marshal(fallbackResponse)
|
|
229
|
+
|
|
230
|
+
return C.CString(string(fallbackJSON))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type contextError struct {
|
|
234
|
+
original error
|
|
235
|
+
context string
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
func (e *contextError) Error() string {
|
|
239
|
+
return e.original.Error()
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
func (e *contextError) Unwrap() error {
|
|
243
|
+
return e.original
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
func wrapWithContext(err error, context string) error {
|
|
247
|
+
return &contextError{original: err, context: context}
|
|
248
|
+
}
|
|
249
|
+
|
|
49
250
|
//export RunLegoCommand
|
|
50
251
|
func RunLegoCommand(message *C.char) *C.char {
|
|
51
252
|
CLIArgs, err := extractArguments(C.GoString(message))
|
|
52
253
|
if err != nil {
|
|
53
|
-
return
|
|
254
|
+
return buildErrorResponse(err, ErrInvalidArguments)
|
|
54
255
|
}
|
|
55
256
|
for k, v := range CLIArgs.Env {
|
|
56
257
|
if err := os.Setenv(k, v); err != nil {
|
|
57
|
-
return
|
|
258
|
+
return buildErrorResponse(err, ErrInvalidEnvironment)
|
|
58
259
|
}
|
|
59
260
|
|
|
60
261
|
}
|
|
61
|
-
certificate, err := requestCertificate(CLIArgs.Email, CLIArgs.PrivateKey, CLIArgs.Server, CLIArgs.CSR, CLIArgs.Plugin)
|
|
262
|
+
certificate, err := requestCertificate(CLIArgs.Email, CLIArgs.PrivateKey, CLIArgs.Server, CLIArgs.CSR, CLIArgs.Plugin, CLIArgs.DNSPropagationWait)
|
|
62
263
|
if err != nil {
|
|
63
|
-
return
|
|
264
|
+
return buildErrorResponse(err, ErrCertificateRequestFailed)
|
|
64
265
|
}
|
|
65
|
-
|
|
66
|
-
if err != nil {
|
|
67
|
-
return C.CString(fmt.Sprint("error: coudn't build response message: ", err))
|
|
68
|
-
}
|
|
69
|
-
return_message_ptr := C.CString(string(response_json))
|
|
70
|
-
return return_message_ptr
|
|
266
|
+
return buildSuccessResponse(certificate)
|
|
71
267
|
}
|
|
72
268
|
|
|
73
|
-
func requestCertificate(email, privateKeyPem, server, csr, plugin string) (*LegoOutputResponse, error) {
|
|
269
|
+
func requestCertificate(email, privateKeyPem, server, csr, plugin string, propagationWait int) (*LegoOutputResponse, error) {
|
|
74
270
|
var privateKey crypto.PrivateKey
|
|
75
271
|
if privateKeyPem != "" {
|
|
76
272
|
parsedKey, err := certcrypto.ParsePEMPrivateKey([]byte(privateKeyPem))
|
|
77
273
|
if err != nil {
|
|
78
|
-
return nil,
|
|
274
|
+
return nil, wrapWithContext(err, ErrInvalidPrivateKey)
|
|
79
275
|
}
|
|
80
276
|
privateKey = parsedKey
|
|
81
277
|
} else {
|
|
82
278
|
generatedKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
83
279
|
if err != nil {
|
|
84
|
-
return nil,
|
|
280
|
+
return nil, wrapWithContext(err, ErrKeyGenerationFailed)
|
|
85
281
|
}
|
|
86
282
|
privateKey = generatedKey
|
|
87
283
|
}
|
|
@@ -96,27 +292,27 @@ func requestCertificate(email, privateKeyPem, server, csr, plugin string) (*Lego
|
|
|
96
292
|
|
|
97
293
|
client, err := lego.NewClient(config)
|
|
98
294
|
if err != nil {
|
|
99
|
-
return nil,
|
|
295
|
+
return nil, wrapWithContext(err, ErrLegoClientCreationFailed)
|
|
100
296
|
}
|
|
101
297
|
|
|
102
|
-
err = configureClientChallenges(client, plugin)
|
|
298
|
+
err = configureClientChallenges(client, plugin, propagationWait)
|
|
103
299
|
if err != nil {
|
|
104
|
-
return nil,
|
|
300
|
+
return nil, wrapWithContext(err, ErrDNSProviderFailed)
|
|
105
301
|
}
|
|
106
302
|
|
|
107
303
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
108
304
|
if err != nil {
|
|
109
|
-
return nil,
|
|
305
|
+
return nil, wrapWithContext(err, ErrAccountRegistrationFailed)
|
|
110
306
|
}
|
|
111
307
|
user.Registration = reg
|
|
112
308
|
|
|
113
309
|
block, _ := pem.Decode([]byte(csr))
|
|
114
310
|
if block == nil || block.Type != "CERTIFICATE REQUEST" {
|
|
115
|
-
return nil, errors.New("failed to decode PEM block
|
|
311
|
+
return nil, wrapWithContext(errors.New("failed to decode PEM block"), ErrInvalidCSR)
|
|
116
312
|
}
|
|
117
313
|
csrObject, err := x509.ParseCertificateRequest(block.Bytes)
|
|
118
314
|
if err != nil {
|
|
119
|
-
return nil,
|
|
315
|
+
return nil, wrapWithContext(err, ErrInvalidCSR)
|
|
120
316
|
}
|
|
121
317
|
request := certificate.ObtainForCSRRequest{
|
|
122
318
|
CSR: csrObject,
|
|
@@ -124,7 +320,7 @@ func requestCertificate(email, privateKeyPem, server, csr, plugin string) (*Lego
|
|
|
124
320
|
}
|
|
125
321
|
certificates, err := client.Certificate.ObtainForCSR(request)
|
|
126
322
|
if err != nil {
|
|
127
|
-
return nil,
|
|
323
|
+
return nil, wrapWithContext(err, ErrCertificateObtainFailed)
|
|
128
324
|
}
|
|
129
325
|
|
|
130
326
|
return &LegoOutputResponse{
|
|
@@ -140,7 +336,7 @@ func requestCertificate(email, privateKeyPem, server, csr, plugin string) (*Lego
|
|
|
140
336
|
}, nil
|
|
141
337
|
}
|
|
142
338
|
|
|
143
|
-
func configureClientChallenges(client *lego.Client, plugin string) error {
|
|
339
|
+
func configureClientChallenges(client *lego.Client, plugin string, propagationWait int) error {
|
|
144
340
|
switch plugin {
|
|
145
341
|
case "", "http":
|
|
146
342
|
if err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer(os.Getenv("HTTP01_IFACE"), os.Getenv("HTTP01_PORT"))); err != nil {
|
|
@@ -157,9 +353,16 @@ func configureClientChallenges(client *lego.Client, plugin string) error {
|
|
|
157
353
|
if err != nil {
|
|
158
354
|
return errors.Join(fmt.Errorf("couldn't create %s provider: ", plugin), err)
|
|
159
355
|
}
|
|
356
|
+
var wait time.Duration
|
|
357
|
+
if propagationWait > 0 {
|
|
358
|
+
wait = time.Duration(propagationWait) * time.Second
|
|
359
|
+
}
|
|
360
|
+
|
|
160
361
|
err = client.Challenge.SetDNS01Provider(dnsProvider,
|
|
161
362
|
dns01.CondOption(os.Getenv("DNS_PROPAGATION_DISABLE_ANS") != "",
|
|
162
363
|
dns01.DisableAuthoritativeNssPropagationRequirement()),
|
|
364
|
+
dns01.CondOption(wait > 0,
|
|
365
|
+
dns01.PropagationWait(wait, true)),
|
|
163
366
|
dns01.CondOption(os.Getenv("DNS_PROPAGATION_RNS") != "", dns01.RecursiveNSsPropagationRequirement()))
|
|
164
367
|
if err != nil {
|
|
165
368
|
return errors.Join(fmt.Errorf("couldn't set %s DNS provider server: ", plugin), err)
|
pylego/lego.so
CHANGED
|
Binary file
|
pylego/pylego.py
CHANGED
|
@@ -10,6 +10,23 @@ so_file = here / ("lego.so")
|
|
|
10
10
|
library = ctypes.cdll.LoadLibrary(so_file)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
@dataclass
|
|
14
|
+
class Identifier:
|
|
15
|
+
"""ACME identifier (domain or IP)."""
|
|
16
|
+
|
|
17
|
+
type: str # "dns" or "ip"
|
|
18
|
+
value: str # Domain name or IP address
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Subproblem:
|
|
23
|
+
"""ACME subproblem details."""
|
|
24
|
+
|
|
25
|
+
type: str # Error type (e.g., "unauthorized", "dns")
|
|
26
|
+
detail: str # Human-readable message
|
|
27
|
+
identifier: Identifier # The identifier that caused this subproblem
|
|
28
|
+
|
|
29
|
+
|
|
13
30
|
@dataclass
|
|
14
31
|
class Metadata:
|
|
15
32
|
"""Extra information returned by the ACME server."""
|
|
@@ -31,11 +48,49 @@ class LEGOResponse:
|
|
|
31
48
|
|
|
32
49
|
|
|
33
50
|
class LEGOError(Exception):
|
|
34
|
-
"""
|
|
51
|
+
"""Unified exception for all errors returned by the lego invocation.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
type: source of the error. "acme" when coming from the ACME server, otherwise "lego".
|
|
55
|
+
code: error code/category. For ACME, this is derived from the ACME problem type; otherwise, it's set by lego.
|
|
56
|
+
status: HTTP status code for ACME errors, None otherwise.
|
|
57
|
+
detail: human-readable description of the error.
|
|
58
|
+
acme_type: full ACME problem type (URN), present only for ACME errors.
|
|
59
|
+
subproblems: list of Subproblem objects with detailed error information.
|
|
60
|
+
info: dictionary with the raw error information returned by the underlying call.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
detail: str,
|
|
66
|
+
*,
|
|
67
|
+
type: str = "lego",
|
|
68
|
+
code: str = "",
|
|
69
|
+
status: int | None = None,
|
|
70
|
+
acme_type: str = "",
|
|
71
|
+
subproblems: list[Subproblem] | None = None,
|
|
72
|
+
info: dict | None = None,
|
|
73
|
+
):
|
|
74
|
+
# Include code in exception message for better error display
|
|
75
|
+
message = f"[{code}] {detail}" if code else detail
|
|
76
|
+
super().__init__(message)
|
|
77
|
+
self.type = type
|
|
78
|
+
self.code = code
|
|
79
|
+
self.status = status
|
|
80
|
+
self.detail = detail
|
|
81
|
+
self.acme_type = acme_type
|
|
82
|
+
self.subproblems = subproblems or []
|
|
83
|
+
self.info = info or {}
|
|
35
84
|
|
|
36
85
|
|
|
37
86
|
def run_lego_command(
|
|
38
|
-
email: str,
|
|
87
|
+
email: str,
|
|
88
|
+
server: str,
|
|
89
|
+
csr: bytes,
|
|
90
|
+
env: dict[str, str],
|
|
91
|
+
plugin: str = "",
|
|
92
|
+
private_key: str = "",
|
|
93
|
+
dns_propagation_wait: int | None = None,
|
|
39
94
|
) -> LEGOResponse:
|
|
40
95
|
"""Run an arbitrary command in the Lego application. Read more at https://go-acme.github.io.
|
|
41
96
|
|
|
@@ -47,25 +102,66 @@ def run_lego_command(
|
|
|
47
102
|
env: the environment variables required for the chosen plugin.
|
|
48
103
|
private_key: the private key to be used for the registration on the ACME server (not the private key used to sign the CSR).
|
|
49
104
|
If not provided, a new one will be generated.
|
|
105
|
+
dns_propagation_wait: optional wait duration for DNS propagation, in seconds (int).
|
|
50
106
|
"""
|
|
51
107
|
library.RunLegoCommand.restype = ctypes.c_char_p
|
|
52
108
|
library.RunLegoCommand.argtypes = [ctypes.c_char_p]
|
|
53
109
|
|
|
110
|
+
if dns_propagation_wait is not None and dns_propagation_wait < 0:
|
|
111
|
+
raise ValueError("dns_propagation_wait cannot be negative")
|
|
112
|
+
|
|
113
|
+
payload = {
|
|
114
|
+
"email": email,
|
|
115
|
+
"server": server,
|
|
116
|
+
"csr": csr.decode(),
|
|
117
|
+
"plugin": plugin,
|
|
118
|
+
"env": env,
|
|
119
|
+
"private_key": private_key,
|
|
120
|
+
}
|
|
121
|
+
if dns_propagation_wait is not None:
|
|
122
|
+
payload["dns_propagation_wait"] = dns_propagation_wait
|
|
123
|
+
|
|
54
124
|
message = bytes(
|
|
55
|
-
json.dumps(
|
|
56
|
-
{
|
|
57
|
-
"email": email,
|
|
58
|
-
"server": server,
|
|
59
|
-
"csr": csr.decode(),
|
|
60
|
-
"plugin": plugin,
|
|
61
|
-
"env": env,
|
|
62
|
-
"private_key": private_key,
|
|
63
|
-
}
|
|
64
|
-
),
|
|
125
|
+
json.dumps(payload),
|
|
65
126
|
"utf-8",
|
|
66
127
|
)
|
|
67
128
|
result: bytes = library.RunLegoCommand(message)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
129
|
+
result_str = result.decode("utf-8")
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
result_dict = json.loads(result_str)
|
|
133
|
+
except json.JSONDecodeError as e:
|
|
134
|
+
raise LEGOError(f"Failed to parse response: {result_str}") from e
|
|
135
|
+
|
|
136
|
+
if not result_dict.get("success", False):
|
|
137
|
+
error_info: dict = result_dict.get("error", {})
|
|
138
|
+
err_source = error_info.get("type", "lego")
|
|
139
|
+
detail = error_info.get("detail", "Unknown error occurred")
|
|
140
|
+
|
|
141
|
+
subproblems = []
|
|
142
|
+
for sub_dict in error_info.get("subproblems", []):
|
|
143
|
+
identifier_dict = sub_dict.get("identifier", {})
|
|
144
|
+
subproblems.append(
|
|
145
|
+
Subproblem(
|
|
146
|
+
type=sub_dict.get("type", ""),
|
|
147
|
+
detail=sub_dict.get("detail", ""),
|
|
148
|
+
identifier=Identifier(
|
|
149
|
+
type=identifier_dict.get("type", ""),
|
|
150
|
+
value=identifier_dict.get("value", ""),
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
info = dict(error_info)
|
|
156
|
+
raise LEGOError(
|
|
157
|
+
detail,
|
|
158
|
+
type="acme" if err_source == "acme" else "lego",
|
|
159
|
+
code=error_info.get("code", ""),
|
|
160
|
+
status=error_info.get("status"),
|
|
161
|
+
acme_type=error_info.get("acme_type", "") if err_source == "acme" else "",
|
|
162
|
+
subproblems=subproblems,
|
|
163
|
+
info=info,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
data = result_dict.get("data", {})
|
|
167
|
+
return LEGOResponse(**{**data, "metadata": Metadata(**data.get("metadata", {}))})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pylego
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.33
|
|
4
4
|
Summary: A python wrapper package for the lego application written in Golang
|
|
5
5
|
Author-email: Canonical <telco-engineers@lists.canonical.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/canonical/pylego
|
|
@@ -63,6 +63,29 @@ On top of the environment variables that LEGO supports, we have some extra ones
|
|
|
63
63
|
| `TLSALPN01_IFACE` | Interface for the TLS-ALPN-01 challenge (when `plugin=tls`). Any interface by default. |
|
|
64
64
|
| `TLSALPN01_PORT` | Port for the TLS-ALPN-01 challenge (when `plugin=tls`). 443 by default. |
|
|
65
65
|
|
|
66
|
+
## Error Handling
|
|
67
|
+
|
|
68
|
+
All errors raised by `run_lego_command()` are `LEGOError` exceptions with structured information:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from pylego import run_lego_command, LEGOError
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
result = run_lego_command(...)
|
|
75
|
+
except LEGOError as e:
|
|
76
|
+
print(f"Error: {e}") # Includes error code in message
|
|
77
|
+
print(f"Type: {e.type}") # "acme" (server) or "lego" (client)
|
|
78
|
+
print(f"Code: {e.code}") # e.g., "invalid_csr", "dns_provider_failed"
|
|
79
|
+
print(f"Detail: {e.detail}") # Human-readable message
|
|
80
|
+
|
|
81
|
+
# ACME-specific fields
|
|
82
|
+
if e.type == "acme":
|
|
83
|
+
print(f"Status: {e.status}") # HTTP status code
|
|
84
|
+
print(f"Subproblems: {e.subproblems}") # Validation details per domain
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Common error codes: `invalid_csr`, `invalid_private_key`, `dns_provider_failed`, `network_error`, `certificate_obtain_failed`. ACME errors include codes like `unauthorized`, `rateLimited`, `dns`.
|
|
88
|
+
|
|
66
89
|
## How does it work?
|
|
67
90
|
|
|
68
91
|
Golang supports building a shared c library from its CLI build tool. We import and use the LEGO application from GoLang, and provide a stub with C bindings so that the shared C binary we produce exposes a C API for other programs to import and utilize. pylego then uses the [ctypes](https://docs.python.org/3/library/ctypes.html) standard library in python to load this binary, and make calls to its methods.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pylego/__init__.py,sha256=7rcUcQcOWsOLxTOEXF2ASkwm_7eED1UIXzxdlgKPr5c,82
|
|
2
|
+
pylego/go.mod,sha256=jNERhSIs38s6uxlVwLn8RRpyvax-jgmQWyBfTLY77VI,11182
|
|
3
|
+
pylego/go.sum,sha256=dHaqyMv51JXWRjlWu_fx_pftQul6kgUo8hJButcuJUI,143991
|
|
4
|
+
pylego/lego.go,sha256=zauvVfyFVfH_4Z1lAiTAYRt-tU-0Z4_xmVjKhkP7uu8,11180
|
|
5
|
+
pylego/lego.so,sha256=wAWQFSFsD2vd1nGr3FTdGwUUfliICwvxGhMnvSf7m9c,87373536
|
|
6
|
+
pylego/pylego.py,sha256=556axo5EwOUGG5ZtsHgfFog-CTkJtOsavdTGLgEAu6k,5522
|
|
7
|
+
pylego-0.1.33.dist-info/licenses/LICENSE,sha256=aklz9Y8CIpFsN61U4jHlJYp4W_8HoDpY-tINlDcdSZY,10934
|
|
8
|
+
pylego-0.1.33.dist-info/METADATA,sha256=NpHliVBR4KXDmSkiL354ciOurbesYKk6hixgGKM7CZQ,6703
|
|
9
|
+
pylego-0.1.33.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
10
|
+
pylego-0.1.33.dist-info/top_level.txt,sha256=pSOYv55_w90qy3xOvqz_ysSz-X-XRTb-jMpiOyLNnNs,7
|
|
11
|
+
pylego-0.1.33.dist-info/RECORD,,
|
pylego-0.1.31.1.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
pylego/__init__.py,sha256=7rcUcQcOWsOLxTOEXF2ASkwm_7eED1UIXzxdlgKPr5c,82
|
|
2
|
-
pylego/go.mod,sha256=2zddNtfcY9OT_qrCABQwRB06dvL0QF9kMA1E6lmvOlM,12467
|
|
3
|
-
pylego/go.sum,sha256=-Gl6lIAbKb0cjJHr4dVvA4_XGBSHONqL0lKRGHUoYn0,187420
|
|
4
|
-
pylego/lego.go,sha256=O_tXZN6DRXlVXJfZdjIXTrDWXyn1UuBeZa_ZzPCnGOA,6004
|
|
5
|
-
pylego/lego.so,sha256=nlASF8T39I3b6NodVaPofEqOs1c5Fu6b0zxvn8KI6MM,87291672
|
|
6
|
-
pylego/pylego.py,sha256=x9NTkBi1P4ITPhGBUgMCHVBHqFN7NXAJAc9aknKLcgk,2263
|
|
7
|
-
pylego-0.1.31.1.dist-info/licenses/LICENSE,sha256=aklz9Y8CIpFsN61U4jHlJYp4W_8HoDpY-tINlDcdSZY,10934
|
|
8
|
-
pylego-0.1.31.1.dist-info/METADATA,sha256=rh0voQ1R8_NHfbYvivFzVlMoF5sNOEi6Ua0ikdA6Ea8,5778
|
|
9
|
-
pylego-0.1.31.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
pylego-0.1.31.1.dist-info/top_level.txt,sha256=pSOYv55_w90qy3xOvqz_ysSz-X-XRTb-jMpiOyLNnNs,7
|
|
11
|
-
pylego-0.1.31.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|