Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions agent/app/service/website_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
existDomains = append(existDomains, strings.Split(websiteSSL.Domains, ",")...)
}
for _, domain := range existDomains {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
if ipAddress := common.ParseIPLoose(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
Expand Down Expand Up @@ -220,7 +220,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
if ipAddress := common.ParseIPLoose(domain); ipAddress == nil {
if domain != "localhost" && !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
Expand Down
20 changes: 20 additions & 0 deletions agent/utils/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,26 @@ func IsValidIP(ip string) bool {
return net.ParseIP(ip) != nil
}

// ParseIPLoose parses an IP address, accepting bracketed IPv6 forms
// such as "[::1]" or "[fe80::1%eth0]" in addition to the bare forms
// that net.ParseIP supports natively. It also trims surrounding
// whitespace. Returns nil for non-IP input, matching net.ParseIP.
//
// This is required because some flows pass the host portion of a URL
// (e.g. "[::1]") rather than a bare IP address. Rejecting that form
// caused 1Panel-dev/1Panel#12646 — the panel SSL self-sign workflow
// reported “domain format invalid” for IPv6 hosts.
func ParseIPLoose(s string) net.IP {
trimmed := strings.TrimSpace(s)
if trimmed == "" {
return nil
}
if len(trimmed) >= 2 && trimmed[0] == '[' && trimmed[len(trimmed)-1] == ']' {
trimmed = trimmed[1 : len(trimmed)-1]
}
return net.ParseIP(trimmed)
}

const (
b = uint64(1)
kb = 1024 * b
Expand Down
36 changes: 36 additions & 0 deletions agent/utils/common/parse_ip_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package common

import "testing"

// Regression test for 1Panel-dev/1Panel#12646: panel SSL self-signed flow
// rejected IPv6 hosts because net.ParseIP does not accept the bracketed
// form (e.g. "[::1]") and the upstream caller passes the host portion of
// a URL rather than a bare IP.
func TestParseIPLoose(t *testing.T) {
cases := []struct {
name string
in string
want bool
}{
{"bare ipv4", "127.0.0.1", true},
{"bare ipv6", "::1", true},
{"bracketed ipv6", "[::1]", true},
{"bracketed full ipv6", "[2001:db8::1]", true},
{"trimmed bare ipv6", " ::1 ", true},
{"trimmed bracketed ipv6", " [::1] ", true},
{"empty", "", false},
{"only brackets", "[]", false},
{"hostname", "example.com", false},
{"bracketed garbage", "[notanip]", false},
{"unbalanced bracket left", "[::1", false},
{"unbalanced bracket right", "::1]", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := ParseIPLoose(tc.in) != nil
if got != tc.want {
t.Errorf("ParseIPLoose(%q) ok = %v, want %v", tc.in, got, tc.want)
}
})
}
}
7 changes: 5 additions & 2 deletions frontend/src/views/setting/safe/ssl/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,11 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
cert: form.cert,
key: form.key,
};
let href = window.location.href;
param.domain = href.split('//')[1].split(':')[0];
// window.location.hostname yields the bare host name and strips
// surrounding brackets from IPv6 addresses (e.g. '[::1]' -> '::1'),
// unlike `href.split('//')[1].split(':')[0]` which incorrectly
// returns '[' for IPv6 URLs. See 1Panel-dev/1Panel#12646.
param.domain = window.location.hostname;
await updateSSL(param).then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
let href = window.location.href;
Expand Down