Skip to content

Commit 1f2c3c3

Browse files
oliwermjuraga
authored andcommitted
MEDIUM: Add support for the ssl-f-use keyword
1 parent 49e94b0 commit 1f2c3c3

32 files changed

Lines changed: 2404 additions & 3 deletions
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2025 HAProxy Technologies
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package params
18+
19+
import "strings"
20+
21+
type SSLBindOption BindOption
22+
23+
// Options accepted by ssl-f-use (sslbindconf).
24+
var sslfuseOptions = map[string]func() SSLBindOption{ //nolint:gochecknoglobals
25+
// bind options
26+
"allow-0rtt": func() SSLBindOption { return &BindOptionWord{Name: "allow-0rtt"} },
27+
"alpn": func() SSLBindOption { return &BindOptionValue{Name: "alpn"} },
28+
"ca-file": func() SSLBindOption { return &BindOptionValue{Name: "ca-file"} },
29+
"ciphers": func() SSLBindOption { return &BindOptionValue{Name: "ciphers"} },
30+
"ciphersuites": func() SSLBindOption { return &BindOptionValue{Name: "ciphersuites"} },
31+
"client-sigalgs": func() SSLBindOption { return &BindOptionValue{Name: "client-sigalgs"} },
32+
"crl-file": func() SSLBindOption { return &BindOptionValue{Name: "crl-file"} },
33+
"curves": func() SSLBindOption { return &BindOptionValue{Name: "curves"} },
34+
"ecdhe": func() SSLBindOption { return &BindOptionValue{Name: "ecdhe"} },
35+
"no-alpn": func() SSLBindOption { return &BindOptionWord{Name: "no-alpn"} },
36+
"no-ca-names": func() SSLBindOption { return &BindOptionWord{Name: "no-ca-names"} },
37+
"npn": func() SSLBindOption { return &BindOptionValue{Name: "npn"} },
38+
"sigalgs": func() SSLBindOption { return &BindOptionValue{Name: "sigalgs"} },
39+
"ssl-max-ver": func() SSLBindOption { return &BindOptionValue{Name: "ssl-max-ver"} },
40+
"ssl-min-ver": func() SSLBindOption { return &BindOptionValue{Name: "ssl-min-ver"} },
41+
"verify": func() SSLBindOption { return &BindOptionValue{Name: "verify"} },
42+
// crt-store load options
43+
"crt": func() SSLBindOption { return &BindOptionValue{Name: "crt"} },
44+
"key": func() SSLBindOption { return &BindOptionValue{Name: "key"} },
45+
"ocsp": func() SSLBindOption { return &BindOptionValue{Name: "ocsp"} },
46+
"issuer": func() SSLBindOption { return &BindOptionValue{Name: "issuer"} },
47+
"sctl": func() SSLBindOption { return &BindOptionValue{Name: "sctl"} },
48+
"ocsp-update": func() SSLBindOption { return &BindOptionOnOff{Name: "ocsp-update"} },
49+
}
50+
51+
func ParseSSLBindOptions(options []string) ([]SSLBindOption, error) {
52+
result := make([]SSLBindOption, 0, len(options))
53+
i := 0
54+
for i < len(options) {
55+
bindOption := getSSLBindOption(options[i])
56+
if bindOption == nil {
57+
return nil, &NotFoundError{Have: options[i]}
58+
}
59+
size, err := bindOption.Parse(options, i)
60+
if err != nil {
61+
return nil, err
62+
}
63+
result = append(result, bindOption)
64+
i += size
65+
}
66+
return result, nil
67+
}
68+
69+
func getSSLBindOption(option string) SSLBindOption {
70+
if builder, found := sslfuseOptions[option]; found {
71+
return builder()
72+
}
73+
return nil
74+
}
75+
76+
func SSLBindOptionsString(options []SSLBindOption) string {
77+
var sb strings.Builder
78+
sb.Grow(64)
79+
first := true
80+
for _, parser := range options {
81+
if parser.Valid() {
82+
if !first {
83+
sb.WriteByte(' ')
84+
} else {
85+
first = false
86+
}
87+
sb.WriteString(parser.String())
88+
}
89+
}
90+
return sb.String()
91+
}
92+
93+
type BindOptionOnOff struct {
94+
Name string
95+
Value string
96+
}
97+
98+
func (b *BindOptionOnOff) Parse(options []string, currentIndex int) (int, error) {
99+
if currentIndex+1 < len(options) {
100+
if options[currentIndex] == b.Name {
101+
b.Value = options[currentIndex+1]
102+
if !b.Valid() {
103+
return 0, &NotAllowedValuesError{Have: b.Value, Want: []string{"on", "off"}}
104+
}
105+
return 2, nil
106+
}
107+
return 0, &NotFoundError{Have: options[currentIndex], Want: b.Name}
108+
}
109+
return 0, &NotEnoughParamsError{}
110+
}
111+
112+
func (b BindOptionOnOff) Valid() bool {
113+
return b.Value == "on" || b.Value == "off"
114+
}
115+
116+
func (b BindOptionOnOff) String() string {
117+
if b.Name == "" || b.Value == "" {
118+
return ""
119+
}
120+
return b.Name + " " + b.Value
121+
}

config-parser/parsers/ssl-f-use.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2025 HAProxy Technologies
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package parsers
18+
19+
import (
20+
"github.com/haproxytech/client-native/v6/config-parser/common"
21+
"github.com/haproxytech/client-native/v6/config-parser/errors"
22+
"github.com/haproxytech/client-native/v6/config-parser/params"
23+
"github.com/haproxytech/client-native/v6/config-parser/types"
24+
)
25+
26+
type SSLFrontUse struct {
27+
data []types.SSLFrontUse
28+
preComments []string // comments that appear before the actual line
29+
}
30+
31+
func (h *SSLFrontUse) parse(line string, parts []string, comment string) (*types.SSLFrontUse, error) {
32+
// Minimum: ssl-f-use <key> <val>
33+
if len(parts) < 3 {
34+
return nil, &errors.ParseError{Parser: h.GetParserName(), Line: line}
35+
}
36+
opt, err := params.ParseSSLBindOptions(parts[1:])
37+
if err != nil {
38+
return nil, &errors.ParseError{Parser: h.GetParserName(), Line: line}
39+
}
40+
return &types.SSLFrontUse{Comment: comment, Params: opt}, nil
41+
}
42+
43+
func (h *SSLFrontUse) Result() ([]common.ReturnResultLine, error) {
44+
if len(h.data) == 0 {
45+
return nil, errors.ErrFetch
46+
}
47+
result := make([]common.ReturnResultLine, len(h.data))
48+
for i, line := range h.data {
49+
result[i] = common.ReturnResultLine{
50+
Data: common.SmartJoin(h.GetParserName(), params.SSLBindOptionsString(line.Params)),
51+
Comment: line.Comment,
52+
}
53+
}
54+
return result, nil
55+
}

config-parser/parsers/ssl-f-use_generated.go

Lines changed: 157 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config-parser/section-parsers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,7 @@ func (p *configParser) getFrontendParser() *Parsers {
561561
addParser(parser, &sequence, &simple.Number{Name: "rate-limit sessions"})
562562
addParser(parser, &sequence, &simple.Word{Name: "guid"})
563563
addParser(parser, &sequence, &quic.Initial{})
564+
addParser(parser, &sequence, &parsers.SSLFrontUse{})
564565
return p.createParsers(parser, sequence)
565566
}
566567

@@ -847,6 +848,7 @@ func (p *configParser) getListenParser() *Parsers {
847848
addParser(parser, &sequence, &simple.Number{Name: "rate-limit sessions"})
848849
addParser(parser, &sequence, &simple.Word{Name: "guid"})
849850
addParser(parser, &sequence, &quic.Initial{})
851+
addParser(parser, &sequence, &parsers.SSLFrontUse{})
850852
}
851853
return p.createParsers(parser, sequence)
852854
}

config-parser/tests/configs/haproxy_generated.cfg.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config-parser/tests/configs/parser_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package configs //nolint:testpackage
1717

1818
import (
1919
"bytes"
20+
"os"
2021
"strings"
2122
"testing"
2223

@@ -101,6 +102,7 @@ func TestGeneratedConfig(t *testing.T) {
101102
for _, configLine := range configTests {
102103
count := strings.Count(result, configLine.Line)
103104
if count != configLine.Count {
105+
_ = os.WriteFile("/tmp/HAGEN.cfg", []byte(result), 0644)
104106
t.Fatalf("line '%s' found %d times, expected %d times", configLine.Line, count, configLine.Count)
105107
}
106108
}

0 commit comments

Comments
 (0)