feat: accept version numbers which are not strings (#1589)

## This PR
Fixes errors when the provided version is not a string but a number
(e.g. versions like 1.0, 2, ...) and improves the test suite to use our
`parseSemverEvaluationData` function. Furthermore, logging now includes
erroneous inputs.

---------

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
This commit is contained in:
chrfwow 2025-03-14 11:38:20 +01:00 committed by GitHub
parent 779e8f929a
commit 6a137967a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 343 additions and 115 deletions

View File

@ -102,7 +102,7 @@ func parseSemverEvaluationData(values interface{}) (string, string, SemVerOperat
}
if len(parsed) != 3 {
return "", "", "", errors.New("sem_ver evaluation must contain a value, an operator and a comparison target")
return "", "", "", errors.New("sem_ver evaluation must contain a value, an operator, and a comparison target")
}
actualVersion, err := parseSemanticVersion(parsed[0])
@ -122,11 +122,17 @@ func parseSemverEvaluationData(values interface{}) (string, string, SemVerOperat
return actualVersion, targetVersion, operator, nil
}
func parseSemanticVersion(v interface{}) (string, error) {
version, ok := v.(string)
if !ok {
return "", errors.New("sem_ver evaluation: property did not resolve to a string value")
func ensureString(v interface{}) string {
if str, ok := v.(string); ok {
// It's already a string
return str
}
// Convert to string if not already
return fmt.Sprintf("%v", v)
}
func parseSemanticVersion(v interface{}) (string, error) {
version := ensureString(v)
// version strings are only valid in the semver package if they start with a 'v'
// if it's not present in the given value, we prepend it
if !strings.HasPrefix(version, "v") {
@ -134,7 +140,7 @@ func parseSemanticVersion(v interface{}) (string, error) {
}
if !semver.IsValid(version) {
return "", errors.New("not a valid semantic version string")
return "", fmt.Errorf("'%v' is not a valid semantic version string", version)
}
return version, nil
@ -143,7 +149,7 @@ func parseSemanticVersion(v interface{}) (string, error) {
func parseOperator(o interface{}) (SemVerOperator, error) {
operatorString, ok := o.(string)
if !ok {
return "", errors.New("could not parse operator")
return "", fmt.Errorf("could not parse operator '%v'", o)
}
return SemVerOperator(operatorString), nil

View File

@ -23,6 +23,76 @@ func TestSemVerOperator_Compare(t *testing.T) {
want bool
wantErr bool
}{
{
name: "invalid version",
svo: Greater,
args: args{
v1: "invalid",
v2: "v1.0.0",
},
want: false,
wantErr: true,
},
{
name: "preview version vs non preview version",
svo: Greater,
args: args{
v1: "v1.0.0-preview.1.2",
v2: "v1.0.0",
},
want: false,
wantErr: false,
},
{
name: "preview version vs preview version",
svo: Greater,
args: args{
v1: "v1.0.0-preview.1.3",
v2: "v1.0.0-preview.1.2",
},
want: true,
wantErr: false,
},
{
name: "no prefixed v left greater",
svo: Greater,
args: args{
v1: "0.0.1",
v2: "v0.0.2",
},
want: false,
wantErr: false,
},
{
name: "no prefixed v right greater",
svo: Greater,
args: args{
v1: "v0.0.1",
v2: "0.0.2",
},
want: false,
wantErr: false,
},
{
name: "no prefixed v right equals",
svo: Equals,
args: args{
v1: "v0.0.1",
v2: "0.0.1",
},
want: true,
wantErr: false,
},
{
name: "no prefixed v both",
svo: Greater,
args: args{
v1: "0.0.1",
v2: "0.0.2",
},
want: false,
wantErr: false,
},
{
name: "invalid operator",
svo: "",
@ -33,6 +103,16 @@ func TestSemVerOperator_Compare(t *testing.T) {
want: false,
wantErr: true,
},
{
name: "less with large number",
svo: Less,
args: args{
v1: "v1234.0.1",
v2: "v1235.0.2",
},
want: true,
wantErr: false,
},
{
name: "less",
svo: Less,
@ -43,6 +123,16 @@ func TestSemVerOperator_Compare(t *testing.T) {
want: true,
wantErr: false,
},
{
name: "no minor version",
svo: Less,
args: args{
v1: "v1.0",
v2: "v1.2",
},
want: true,
wantErr: false,
},
{
name: "not less",
svo: Less,
@ -206,13 +296,20 @@ func TestSemVerOperator_Compare(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.svo.compare(tt.args.v1, tt.args.v2)
var operatorInterface interface{} = string(tt.svo)
actualVersion, targetVersion, operator, err := parseSemverEvaluationData([]interface{}{tt.args.v1, operatorInterface, tt.args.v2})
if err != nil {
require.Truef(t, tt.wantErr, "Error parsing semver evaluation data. actualVersion: %s, targetVersion: %s, operator: %s, err: %s", actualVersion, targetVersion, operator, err)
return
}
got, err := operator.compare(actualVersion, targetVersion)
if tt.wantErr {
require.NotNil(t, err)
} else {
require.Nil(t, err)
require.Equalf(t, tt.want, got, "compare(%v, %v)", tt.args.v1, tt.args.v2)
require.Equalf(t, tt.want, got, "compare(%v, %v) operator: %s", tt.args.v1, tt.args.v2, operator)
}
})
}
@ -385,6 +482,130 @@ func TestJSONEvaluator_semVerEvaluation(t *testing.T) {
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"versions given as double - match": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"if": [
{
"sem_ver": [1.2, "=", "1.2"]
},
"red", "green"
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"version": "1.0.0",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"versions given as int - match": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"if": [
{
"sem_ver": [1, "=", "v1.0.0"]
},
"red", "green"
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"version": "1.0.0",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"versions and minor-version without patch version operator provided - match": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"if": [
{
"sem_ver": [1.2, "=", "1.2"]
},
"red", "green"
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"version": "1.0.0",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"versions with prefixed v operator provided - match": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"if": [
{
"sem_ver": [{"var": "version"}, "<", "v1.2"]
},
"red", "green"
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"version": "v1.0.0",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"versions and major-version operator provided - no match": {
flags: Flags{
Flags: map[string]model.Flag{

View File

@ -1,30 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFJTCCAw2gAwIBAgIUHkLmoT3U1jDYbXAqX84fjR/Qw5kwDQYJKoZIhvcNAQEL
BQAwITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBjZXJ0aWZpY2F0ZTAgFw0yNTAxMDgw
OTI0MThaGA8yMDUyMDUyNTA5MjQxOFowITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBj
ZXJ0aWZpY2F0ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQK8r5J
7wOvdMsbGy63wlOie97lMHT/HSfneyTEnvRM4ZenKNjrKI6zlyYMQX9/RQs9qCxF
QQDFMhLoMrQKkDptVur+PhYCa/uAWUtTFVXJMbJ2Zdzbg86WCgOlRLwMqGiwnu0l
25dk5Ja5nE4HSSg5SipLxtCCdxT3n5YmQBO+Kkyc1Uxgk6yQMQcdQENGhQHazVzy
fAsrutJFZNtkYXObR9t6/MyYgg3zFjNRCpOYhc9misT54TVteb9L0smLqMhVfQkh
CFWkVEWmaeGyTYAR7gGvFy9b7N/45FfBvlumgR7KiG/uLJNadCQtf7v3pRN36SBH
aZleAp8KWmy6N2IMuWx/hMQCVnzoi05gvYkCWJSoseobhiwaXsDFQCc8ZB/c2H9C
yMmyKRL+c3RM7lI/InxLmpS94xJIhDuvDiEPYTWqY3BPqCvAly8LXEYBqgJNsnOa
+pdoJQ/rl87pIdDt3CK/wWQ1GIlp1v7aYY53riM4i0Hvnk6SPNBgpUXbqAZjpnOb
fsIrKGO/FAJ5Hc3mfiuTj15jDoxPPUPqzj4yP2h7WqbdYwEpDvTd0Cd+tjgCuQUj
JU2MpTcNUQQQFdXASZQgfXHOTW873zIx4mourcO7jkEDwId13H9QMU4JPG9ZsLG6
p08S26yx8G0chV/Q7BNVfOTCKAc8GH+94CynAgMBAAGjUzBRMB0GA1UdDgQWBBQs
ubjWSGXffy7mMQGV2EdbdUwtPjAfBgNVHSMEGDAWgBQsubjWSGXffy7mMQGV2Edb
dUwtPjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBwJyNT3ldS
DsvOOFH5VT+yMByXv690FH7aYBu5VeSpd8oZPJZgzRh58fTzUxrnPk7iyuQzlgCw
ZZssvObDEA1eK9vG1svomY8yu8VrpKG/yl0YItnZpuQTvCfJsNOm1Qe0j8FPJVOu
Jgges3i+QV18o2dmtQKTTWdExu2J30eQgL8SB7hG9vE6tfcVkzCw6+5ev+fyO9kd
DxrN9YY/FKjNMY2WSCjbQ99NFNS3Qf0MFLLy+V3oiYlT1/qkuX9omLIL6qDabuXE
8XbppIG+ku610hiVRd4ZZyTH1EIThqevs7y3zQYFQNDL8wPfYHRTl00Fto/NLcTH
+cAhf5nC5R4PqvSJZ1IZkxUj/8oljUtMsbFKTekgJGnP/VzDFoNmYO6lW3rBFM1L
ovFJhaesnAKQ6WFk+Yru9kFylX2dHqFsB0SlggGS+r8kwbrXlXJhWAE/WpPVyU+H
IUyYluNLKKvMBYa9GqUC6C7XlZmiuemZy0oCy4gHqdmII09Bj3C3nT7+Ms4D21jh
fZKN4WZcpsA2XaE27XsAqJp2gIV+SL3VqD3hAOFIRWAwQVvTlVJ1GWM1DJBVnLpp
BFK4mO0Mga0AbzJ6VL9ElvXVjqwRl8gbeT7yHWQ8A0r7yl4n0i4FloDeoCmr7i9O
9dqUWaknNa6rIUKwvIodkn39C1G7uCpeLw==
MIIFJTCCAw2gAwIBAgIUKd5pSsJ6Fxr2f4vF2a+MOlQAvcowDQYJKoZIhvcNAQEL
BQAwITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBjZXJ0aWZpY2F0ZTAgFw0yNTAzMTIx
MjEzNDdaGA8yMDUyMDcyNzEyMTM0N1owITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBj
ZXJ0aWZpY2F0ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALqvg928
yzEpRGpbK/gQiVC/tH3CzrieP+QHDI9D0Hlzi8F2V9YxYbmGutewd3mkNiiaVfuo
Ue5HRYwhMBKiMhUByZc2a5wF/eftPE2Hj5rDFiOnW5duDERLMLjFE4lOwnuCZtNk
Ljt5FEd7Q6TbDfOW4ETxwdbQObS+neSXmqYrWQvdIvN4jKyHkiMqdMwGZp6AYYMz
FVEKEQ76xbNkTiMjhOfaCZklZp4D99DNIANtYcpf3+VRZcN4xkVe7+wP8ofioZ/M
CwAQAW/2gq2eienTQ+XGHUZfig0JslinuTZy15wr+1lnYzNvJtK/30Z+pmhjkQBN
f2d0Be6Gf3RUTEFlXqysY1aDEbvW+8lSKyuLr/H73O1vGkhMfJ1A5dak+vwhk40g
8twYSJesDGjNNW/rxglSZcCA1sPu39gLC+FHZ3eTnur5JOwLvM7n0sn1Ztmkq//7
eCY+ZDT/X39UwluMa9SFugdqfqOLpCCZtkMCLxjNoDLPmEqCHS5E2q3yrl6RzNlg
yc78dRaChgTQbbS2EX0TXqCDnNpyuQAl9hwcMbh1Law0iNGuRircZW6v4Mkc6se0
rpDmtGy9E/wrr2XGsD5KtjpVF2rUHXvpzoY+ioOtiOrDdnLBAwRyw8bIsynLlbpb
F8K7H5b7x1RI8d9AAZohEl9c9iqMyvJnaZTrAgMBAAGjUzBRMB0GA1UdDgQWBBTj
lAwMUauC+x+73IzYJOxNnqJnMDAfBgNVHSMEGDAWgBTjlAwMUauC+x+73IzYJOxN
nqJnMDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBWGKra39+p
WGpobGClcmlP+n5umtzOolFNTM5OyVmNaqWLtcigldFwpPFW7lhYIbbmxWSDpFto
DDkv1FuiEulo7C4TVEa6x/RsgCfOMR5WCsXOh65moSN86SFMPbnSuajpGuY5RfqH
wxfAyd9/IO4aqwINQE0P4VNgqhOMJ66LmodPsGjXC2mkLDePV3sswka+dv1CtFWU
/9Zp7n0UwEga3JLw5RTnXswpA5lyIkRnda9m9ydL5A5uKe3tCwSEUOUGphjdi6Y0
ycAwsD1G7XwK60seZyX3MUMEo4CiH21WC3P2c8xRVl7TxQEj2m+NlQ3UArgIdl60
4FE7p+yorrXEi43fwLHFi17P7LGvnurp9H6Xs5YZPGkYwEOD6HxDpGHaYlvHArDe
r2+pw/o8YJqP3E9YjVK5wby4v32DdBSGykyJTtXWj5JkwmILUzTE0skDk7fviiyO
F5nwMt37bSliU5p8mk9YzJNv+tTqRGEsOj3NjnjVX1BSDvuKeZfiLG08LRaQrhWa
6ZU+BPtTi0JkeczEpreSNMPnKytGRkXQ3AXhkeQYLBoAbypzp0zzw6yJ6ADBbUZu
Kn3iuQR7nqpdnE7hNh21mO7tKGV0il4ePQUIeM+E3MJNW4KiSN5UV+Z9GxeQQudD
zJCP+qbCnwOTm+ZDZOymqCoMILaWVL0/Qw==
-----END CERTIFICATE-----

2
flagd/pkg/service/flag-sync/test-cert/gen.sh Normal file → Executable file
View File

@ -13,5 +13,5 @@ openssl req -x509 -newkey rsa:4096 -days 9999 -nodes -keyout ca-key.pem -out ca-
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/CN=flagD test server PR and CSR"
# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf
openssl x509 -req -in server-req.pem -days 9999 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf

View File

@ -1,29 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIE6TCCAtGgAwIBAgIUNCaL1+pmzRcfRNGaIgTrD2t3lqswDQYJKoZIhvcNAQEL
BQAwITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0yNTAxMDgw
OTI2MTJaFw0yNTAzMDkwOTI2MTJaMCcxJTAjBgNVBAMMHGZsYWdEIHRlc3Qgc2Vy
dmVyIFBSIGFuZCBDU1IwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC3
8cZVWtUihF8lQmmKE8ZG896s7KSqPoNDdqrzp0pebwbErHf/iVarUsl9IWuxxCeG
Oh65yRQfZH8zp39Yf6oAxgiF+sFITkoWrGwB5HbT6qRwUQC5kLytuY9THTJKcpyo
UNTsR46Ne0ix+yM+55nI504tK4U8UdVnQkbnU0tnRlb4LOSA4emDYnCWsl814l1T
Np4h1e7Sb8ppwDju07UQq92Kt4oSZIjHQXeJmOt4vko4c8rv3xfUA12cPSajUEw7
oiohwd50LKtIm4MyG3jisTvie46xIGHD9AJ0AejuwsSSrAUhZ6S+fVwFVdzPpo9C
SKjjrz0fw6FkSeN3Kc61ipYKHf44dsZZB/IQB8CMpLk3cotktu8lwyTDb9URAg0c
PpBLZl7PFY4LgMZrIwsUy5CZbcmkzPRqNVfmfKMCy8D/CNV7jhxSj8FxD8zcTn63
lrbmMoAIZtDZKBqpzRqVkawxVGPjvn4wXVi/CJC5o04l9bWpXJaGLgS0SVtLT8Iz
yWYPXYLadMY7UIn7mqWBRb+x9GHhWGibJLHOD7+oQA+m3/7Sj9fbRHSbKVl8zoRx
Fmn8r2104ym+Cgqh54eUg73lc6D1bXAgMbwJu5qbF9rHn3IpKZSI19UB/2RlsxMX
py8PrGlKEhS1vwiZDyTe5jEzmgfKzzCe+274JP+ZlQIDAQABoxMwETAPBgNVHREE
CDAGhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4ICAQBxCHpqFX3IA+2tE5Sjf+Y3La7q
DogiNDsSj5Ngu3wxs76pbUDepSesI4EmpCnaSsjTfChCd24ZXa5seeoK8P8BTHcm
yAmWy6G03MvFovnuOlxfBlXhhhCywUbevLPHw1Md9+I8OZP13IYF+agv9CktGulp
keYJKcaEvaxS3dLe4GIJC5KVy/hUz47U0yZQbpqlzKkuDQ1RrWoJFh2sSpaPc1Dy
/3QwB1IC+WeP6dkDhxikoPHodpPvcrh5S9/HvihE9AUo682wi9MJ0BfhWt6zDJb/
WJaYQ0J5whS5PtXhdWdobte6Pz7cInusd2OF1BbR2tc/JBB4VfQWUH1ok3AKQ3kq
5yq7+qA/QWeuQiIUmxHtfzfKcfi5QvDgNoNRAp7yHpAXXmYKBfBYF/5JaRon9FHg
LaFXEiexoOYZpJkAPbsU3fiSSC+QRRo1imqnLx3L52BUn0/0Z7sLpBL26nRrr4z9
3LxgKxpPyBX7/Wv3GPMXPkXP5JVqFs6vMUXrAvg/prLJpvIQWvl4/YiIibmKGONy
BK9tYSdV1b/MrTMnzXYgh2/XZ3HaAU7TdQefk3Cs4/sD+l8pRZGvysaxhUoX3Ua2
jrOcfhqlr/XbeBWrHosJB5HVbpgGKJ/Dix6PuRqGcRyi3QgzavUyQ81V7MyhtVMI
7AWcs98UoJz2+7oLbA==
MIIFKzCCAxOgAwIBAgIUQlEn6qb8OCeRl1QsEFJqnLnk+DcwDQYJKoZIhvcNAQEL
BQAwITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBjZXJ0aWZpY2F0ZTAgFw0yNTAzMTIx
MjEzNDhaGA8yMDUyMDcyNzEyMTM0OFowJzElMCMGA1UEAwwcZmxhZ0QgdGVzdCBz
ZXJ2ZXIgUFIgYW5kIENTUjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AOgX5FyznO1ayOzN8I2n7XBicyzMz6qFxHOb/MdY+JMiMTkhalyEFRMjwK6cW24a
twk8MorUPtbZyE8MEL18OqroEeuiJTRX8BQbXXfK1yC39HkBv7Rjnh3pacenl2TY
E8U47PEkrNgEqlIn0lK/5j8KZ2IZY/h4LIWcajFfTnURPdYBn8V/nkrA/l4dVrfb
dYzxy3BGIE5AVvtv6kh267F5YfRklRtod1IN9BURjILi8YPu1MKqclr1xhu74eMW
oJfp/JSw4E6saOyAfX3/agkeotCITeMVAvuS23DdTMiCG4BWxriCE8O32/7JwhaU
FJpWoZfcnUtTZOpz6fCNTswDGMNN4hbeoHNrYCkp207l/AsIpENQHN8qxlp/fu9p
WAzm7hXDiYfbhKV8uePb9S7YIPZGhG5OPGVQvAzHdCEfkBji4fwYK63coITriu+S
BPWsWfN8bq9kxFtOufKJX4SNtMzFHQdLo+Zb++65C42NmdcOEMXzu9ppbGr8TQh/
EaZHMkaAEJf0rWjU7W0hmVUnOtV3XYJVJ6juTCm4vEDNnLQlmdz0jsfYYibGIzwZ
yMfmNUPLRGjqKedRB+4/7HQ07swnmRuu74KvH3WI20RfdJU5VI87JL88+oFuUB4Z
P6Vv11dIOEdAoAj8iYbfvOQYHydm8f+//0cf5WSB7QsLAgMBAAGjUzBRMA8GA1Ud
EQQIMAaHBAAAAAAwHQYDVR0OBBYEFFdKZlUJY5zYX5cXpkWVETEzOGMCMB8GA1Ud
IwQYMBaAFOOUDAxRq4L7H7vcjNgk7E2eomcwMA0GCSqGSIb3DQEBCwUAA4ICAQBf
dOL20Sq/utYKPESS7FDy6C3P7pRxB1l+CFNQNXFqhLZjqpo/vxXwY+e26mlSU/O7
rV48eAgscuWbajdkNFQtRq59Dr1uJYnTLn8s4vMxn+5qaPSLE/TyA4rt+cjdvi8i
SCegVSsF4YuSt90+b7ZUkdwu6+HeEUauCwSkV7tUh1IZ4UO75iJtRMZxiBfJ3R8y
encOH/dkS3Io98IqEUgqIc3R32jSadwovrgySwMV2frvUT+JfxvodtIDfCwAFQvS
GtbOxfV7lyyPE12HXgv19hgL3IAarZPdJvEusx1uOKBVDuUJooWU2ByJseHB6TIb
sZdrLbzfydl+e2aJzEub4x6BNgBW+7pFkoF31UHq81KZyvAByFWqMPjhpcZHFbjG
izVB0Wz02QxPMX094UUl0EBUwDhFueDASnWfItrPaIbboXQKlg3Va/IJDolAACTp
bTz4UmtJ7G8HEMf+VpYBAVcL0TYbpn+oIqR7Myo1lbeVkIatn8U7+x8LpJ2ZZg6h
sMsP8fDf0aFdYiWUMo3dj54XbFkkxIXEfUptr0lY3NqxqloQOHZwKNLVPAx7PO+Z
Gt8NwiEiFdgghTOOXLvhbXBaaj3SZHxRX39/ZYBB+CjH/AHihqsQ7jIWPV9vk/oU
Tli94kf5sC2bQveDRP6+Zybjb5uYDbDQmurD09c+Mw==
-----END CERTIFICATE-----

View File

@ -1,52 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJRQIBADANBgkqhkiG9w0BAQEFAASCCS8wggkrAgEAAoICAQC38cZVWtUihF8l
QmmKE8ZG896s7KSqPoNDdqrzp0pebwbErHf/iVarUsl9IWuxxCeGOh65yRQfZH8z
p39Yf6oAxgiF+sFITkoWrGwB5HbT6qRwUQC5kLytuY9THTJKcpyoUNTsR46Ne0ix
+yM+55nI504tK4U8UdVnQkbnU0tnRlb4LOSA4emDYnCWsl814l1TNp4h1e7Sb8pp
wDju07UQq92Kt4oSZIjHQXeJmOt4vko4c8rv3xfUA12cPSajUEw7oiohwd50LKtI
m4MyG3jisTvie46xIGHD9AJ0AejuwsSSrAUhZ6S+fVwFVdzPpo9CSKjjrz0fw6Fk
SeN3Kc61ipYKHf44dsZZB/IQB8CMpLk3cotktu8lwyTDb9URAg0cPpBLZl7PFY4L
gMZrIwsUy5CZbcmkzPRqNVfmfKMCy8D/CNV7jhxSj8FxD8zcTn63lrbmMoAIZtDZ
KBqpzRqVkawxVGPjvn4wXVi/CJC5o04l9bWpXJaGLgS0SVtLT8IzyWYPXYLadMY7
UIn7mqWBRb+x9GHhWGibJLHOD7+oQA+m3/7Sj9fbRHSbKVl8zoRxFmn8r2104ym+
Cgqh54eUg73lc6D1bXAgMbwJu5qbF9rHn3IpKZSI19UB/2RlsxMXpy8PrGlKEhS1
vwiZDyTe5jEzmgfKzzCe+274JP+ZlQIDAQABAoICAQCh2ZHazqaUzYZuYWY9wTKI
gdIfs8Ubqw+Sj9rRsxQjzWtWKC8Z4H0rGBgECyEYdHEWkRMyA7S5/pJSIAJUG1i5
f4ZGZSImfgSAuMv8SksoIeD4lr2dibYK4igzSJBUo04mZ6FCGaBb6utG96PGmMBe
3u+RnSaJsbOlPNLofgjt4R1rFw0kPiNaoIZSgrZ10iytqHQxb2zJKuYecK1nr041
UhQIF4DcuCsFsBv/LVebkUv7Kh+ZOmJcAW4fqErUDjZVjlWmCFC1RgycQYGJ2FRg
mvQHTxJ51fVQFucFrhyH4UZXjBajku+JUQJkC23UJEkPWKGKXUnaJide+Ai2dEnV
Qy84Z/6cLYxBNxFTr1L1+kcdMu163rI+tJhiNyvL3uvuedzI/QBIIJwcy9s5VotK
/36ocdgVhI2xDowJvVVdMDVSsAd573CiwiwPyGqBLKvc90K1M1g/PSn4E4vJmSwh
OmZUoh8NL42MiX3lavf7tqjlWiANOWiI7q+J5jK2a/HAlYQLGnNxm6NYSzfpp5x4
I7QBZJopTwTCgD3JhGCu6JTmYMTbcxjvKpLAFa2WyqOVI0K5Yk9XLaepsqRQsGUL
lam8Gv/vyv+qsMKSh3W2ExDrOI72P8FkyyuzgS2TfHM4hZyJfp18sQGC5HgQk3EM
LbVY1ZXPScFOxYNAm5yUQQKCAQEA2b+GX/YUBkTKsC6zGmIAUTzS6Tprrvpy7Fhr
fJXdKKCPqxjwmDYYK17fBR5I4plASeSBR6OzILa6aioyi4NmNh0TUFuxnZ4pJhl2
I9GwKxLqJCy65TivelRrkxFhzOzZ5PqAWC6QrmmWonI9zlKMfoTbdv0spWbKT0SU
RlX8jCqMKzFZB8WhHuR3vQHex7EizJbSltVRnDUFI9FW9z1/0lYLDdzu3M7/PP71
tR3t36yW98slrJrQ/SXTwx8AEUbpmqNh/fTg1e/eaXs4E9xL+xZ50J3nJjRTMJXn
1UiXY/GLjCE8HVEStpNE5u743VKC1sId2liFFmT9knJR8LepRQKCAQEA2EILQnE7
XA07Bso1ryXVQY8e3j+g+V8uW7AXgmM0x0WlBBtaPzQfj8YsxZB766PHvDdyzfKX
a7Q3hh0jWi/KhE52TgxmvHbssypF+QcBXRbVVxZ0B0jaqzrZADodSN3hFDOUhsKQ
T55sH6Nc+casMEb8EoH0vzkQZV6sI6ggKQ4Oe6IcSMqU+5jpxCkMDvP2MvZxzm2Y
yCyizEFCYHCQLC6cMxxcg6snuUODZtO2o2XTfgLe7Se4RoijrOPHAC8q3qNuX58P
fJrYmw7/k50qkWQK2s0thaKF/uWApj1CLsOxfavdiKBdC8pNHr65RCvsJc1Jj8xN
aJA19XWbBHesEQKCAQEApRAavQO9ikL7ozLDcmx38R06hLJUjwArvh4I3Rh93h5Y
ykrNl5TqHXZ9eVPLzHp/0YP2vGfLkjDyfygdyMSC5uKDkZbwvZr3dno2pFCASya7
d1CxHLIr03/LTGEQ0ld5laqPQEmMQ6qnFd2kHJNXDVGJTFn/TiLtmclS3T6xg099
kgCGjO2zhceLPSv9xULyLkTmvpBWnSNUEiLO2f00uC2hk5C3QYto0MQ1Xmahu70J
dC37ES0K39uc+3y0gGRREXhpACpxhbufzjYp/GQy9NPE4+/PGZbwuRPp+jRdDtY8
Aq3u9ApRNTXONYFSBfRWWpYsKyiPOrqzviALHX8cQQKCAQEAgixXDLqOCZ3pLvAf
GnvCf4EACrXwVstFY2l+7Tx8M4snhm5Uh4D/kpKutol/Hltqyk/yKifhn7JOTctS
UWI9HCECs35hhQZs+nfywLDH0FoDNzXLx+rBvZphrvJMWGU+q+NUfz20kkiBOxYh
zDQbx7+i0h0pzsUxqmMvaRM1sKDGdQMi1Woj/cKQzEQM/x84znpsDN8JvUyo/hw2
MUjwb7fqzBVBVvx6n9kUypub74VGpi5iNAzZrpNnOpWtXt4FhxiHQsXDE7U9tzBz
BU7wpa27nvMseKlY0RMium5bXTzspQIECs7E02kFvQD/EhsCPcrxgb5vxgYwhL0y
/6BtkQKCAQEA0PCdcoVCbhkSPlhrKOeBFtkexcHIlcqBvfWGUzrO9oqXHVncU3JG
F7qxgur5jXGMcJtbzKGzTh8o6nb7dJYkN6ozpgwACWPcuD/uMqMIGr9oTfVWiLdl
whi1MqX6IyA6568HEtDcNjDfdQ8efJ8PQXB5h4DzKj7EC4Z5oPUFMNWbDPMvh7ER
9k07wqe/ZHb3bxB3VVy0jN7fbMP03wFvpiDU2IONekrwx3UYcHXCZbjt4/PuFTVk
++mrDNq4EMXed8oF/Zk+s8wnKqKWWiEwvnZUn9mUwZONJ+PnisW9Xn+Rw3EO97YT
aFIhPf94JnJUQ/J9xwe2MvBIGtpAERp2gw==
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
8zybxf+aKen3eqefu4M/U0ah67PPDA==
-----END PRIVATE KEY-----