mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
refactor: auto certificate options
1. Add OCSP Must Staple options #292 2. Add LEGO_DISABLE_CNAME_SUPPORT options #407
This commit is contained in:
parent
532d6e83c5
commit
4660a46a7e
18 changed files with 234 additions and 212 deletions
|
@ -117,14 +117,16 @@ func IssueCert(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = certModel.Updates(&model.Cert{
|
err = certModel.Updates(&model.Cert{
|
||||||
Domains: payload.ServerName,
|
Domains: payload.ServerName,
|
||||||
SSLCertificatePath: payload.GetCertificatePath(),
|
SSLCertificatePath: payload.GetCertificatePath(),
|
||||||
SSLCertificateKeyPath: payload.GetCertificateKeyPath(),
|
SSLCertificateKeyPath: payload.GetCertificateKeyPath(),
|
||||||
AutoCert: model.AutoCertEnabled,
|
AutoCert: model.AutoCertEnabled,
|
||||||
KeyType: payload.KeyType,
|
KeyType: payload.KeyType,
|
||||||
ChallengeMethod: payload.ChallengeMethod,
|
ChallengeMethod: payload.ChallengeMethod,
|
||||||
DnsCredentialID: payload.DNSCredentialID,
|
DnsCredentialID: payload.DNSCredentialID,
|
||||||
Resource: payload.Resource,
|
Resource: payload.Resource,
|
||||||
|
MustStaple: payload.MustStaple,
|
||||||
|
LegoDisableCNAMESupport: payload.LegoDisableCNAMESupport,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,21 +5,27 @@ export interface DNSProvider {
|
||||||
code?: string
|
code?: string
|
||||||
provider?: string
|
provider?: string
|
||||||
configuration: {
|
configuration: {
|
||||||
credentials: {
|
credentials: Record<string, string>
|
||||||
[key: string]: string
|
additional: Record<string, string>
|
||||||
}
|
|
||||||
additional: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
links?: {
|
links?: {
|
||||||
api: string
|
api: string
|
||||||
go_client: string
|
go_client: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface DnsChallenge extends DNSProvider {
|
|
||||||
dns_credential_id: number | null
|
export interface AutoCertOptions {
|
||||||
challenge_method: string
|
name?: string
|
||||||
|
domains: string[]
|
||||||
|
code?: string
|
||||||
|
dns_credential_id?: number | null
|
||||||
|
challenge_method?: string
|
||||||
|
configuration?: DNSProvider['configuration']
|
||||||
|
key_type: string
|
||||||
|
acme_user_id?: number
|
||||||
|
provider?: string
|
||||||
|
must_staple?: boolean
|
||||||
|
lego_disable_cname_support?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto_cert = {
|
const auto_cert = {
|
||||||
|
|
|
@ -3,18 +3,19 @@ import type { SelectProps } from 'ant-design-vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { AcmeUser } from '@/api/acme_user'
|
import type { AcmeUser } from '@/api/acme_user'
|
||||||
import acme_user from '@/api/acme_user'
|
import acme_user from '@/api/acme_user'
|
||||||
import type { Cert } from '@/api/cert'
|
import type { AutoCertOptions } from '@/api/auto_cert'
|
||||||
|
|
||||||
const users = ref([]) as Ref<AcmeUser[]>
|
const users = ref([]) as Ref<AcmeUser[]>
|
||||||
|
|
||||||
// This data is provided by the Top StdCurd component,
|
const data = defineModel<AutoCertOptions>('options', {
|
||||||
// is the object that you are trying to modify it
|
default: () => {
|
||||||
// we externalize the dns_credential_id to the parent component,
|
return {}
|
||||||
// this is used to tell the backend which dns_credential to use
|
},
|
||||||
const data = inject('data') as Ref<Cert>
|
required: true,
|
||||||
|
})
|
||||||
|
|
||||||
const id = computed(() => {
|
const id = computed(() => {
|
||||||
return data.value.acme_user_id
|
return data.value?.acme_user_id
|
||||||
})
|
})
|
||||||
|
|
||||||
const user_idx = ref()
|
const user_idx = ref()
|
||||||
|
@ -35,7 +36,7 @@ watch(id, init)
|
||||||
|
|
||||||
watch(current, () => {
|
watch(current, () => {
|
||||||
if (mounted.value)
|
if (mounted.value)
|
||||||
data.value.acme_user_id = current.value.id
|
data.value!.acme_user_id = current.value.id
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -84,8 +85,9 @@ const filterOption = (input: string, option: { label: string }) => {
|
||||||
<AFormItem :label="$gettext('ACME User')">
|
<AFormItem :label="$gettext('ACME User')">
|
||||||
<ASelect
|
<ASelect
|
||||||
v-model:value="user_idx"
|
v-model:value="user_idx"
|
||||||
|
:placeholder="$gettext('System Initial User')"
|
||||||
show-search
|
show-search
|
||||||
:options="options"
|
:options
|
||||||
:filter-option="filterOption"
|
:filter-option="filterOption"
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
|
@ -52,12 +52,6 @@ function save() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
provide('data', data)
|
|
||||||
|
|
||||||
provide('no_server_name', computed(() => {
|
|
||||||
return false
|
|
||||||
}))
|
|
||||||
|
|
||||||
const log = computed(() => {
|
const log = computed(() => {
|
||||||
const logs = data.value.log?.split('\n')
|
const logs = data.value.log?.split('\n')
|
||||||
|
|
||||||
|
@ -134,9 +128,17 @@ const isManaged = computed(() => {
|
||||||
</AForm>
|
</AForm>
|
||||||
|
|
||||||
<template v-if="isManaged">
|
<template v-if="isManaged">
|
||||||
<RenewCert @renewed="init" />
|
<RenewCert
|
||||||
|
:options="{
|
||||||
|
name: data.name,
|
||||||
|
domains: data.domains,
|
||||||
|
key_type: data.key_type,
|
||||||
|
}"
|
||||||
|
@renewed="init"
|
||||||
|
/>
|
||||||
|
|
||||||
<AutoCertStepOne
|
<AutoCertStepOne
|
||||||
|
v-model:options="data"
|
||||||
style="max-width: 600px"
|
style="max-width: 600px"
|
||||||
hide-note
|
hide-note
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,13 +5,6 @@ import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
||||||
import cert from '@/api/cert'
|
import cert from '@/api/cert'
|
||||||
import WildcardCertificate from '@/views/certificate/WildcardCertificate.vue'
|
import WildcardCertificate from '@/views/certificate/WildcardCertificate.vue'
|
||||||
|
|
||||||
// DO NOT REMOVE THESE LINES
|
|
||||||
const no_server_name = computed(() => {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
provide('no_server_name', no_server_name)
|
|
||||||
|
|
||||||
const refWildcard = ref()
|
const refWildcard = ref()
|
||||||
const refTable = ref()
|
const refTable = ref()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
||||||
import type { Cert } from '@/api/cert'
|
import type { AutoCertOptions } from '@/api/auto_cert'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
options: AutoCertOptions
|
||||||
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
renewed: [void]
|
renewed: [void]
|
||||||
|
@ -13,12 +16,12 @@ const modalClosable = ref(true)
|
||||||
|
|
||||||
const refObtainCertLive = ref()
|
const refObtainCertLive = ref()
|
||||||
|
|
||||||
const data = inject('data') as Ref<Cert>
|
|
||||||
|
|
||||||
const issueCert = () => {
|
const issueCert = () => {
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
|
|
||||||
refObtainCertLive.value.issue_cert(data.value.name, data.value.domains, data.value.key_type).then(() => {
|
const { name, domains, key_type } = props.options
|
||||||
|
|
||||||
|
refObtainCertLive.value.issue_cert(name, domains, key_type).then(() => {
|
||||||
message.success($gettext('Renew successfully'))
|
message.success($gettext('Renew successfully'))
|
||||||
emit('renewed')
|
emit('renewed')
|
||||||
})
|
})
|
||||||
|
@ -52,6 +55,7 @@ provide('issuing_cert', issuing_cert)
|
||||||
ref="refObtainCertLive"
|
ref="refObtainCertLive"
|
||||||
v-model:modal-closable="modalClosable"
|
v-model:modal-closable="modalClosable"
|
||||||
v-model:modal-visible="modalVisible"
|
v-model:modal-visible="modalVisible"
|
||||||
|
:options
|
||||||
/>
|
/>
|
||||||
</AModal>
|
</AModal>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import type { Cert } from '@/api/cert'
|
|
||||||
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
||||||
import DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
|
import type { AutoCertOptions } from '@/api/auto_cert'
|
||||||
import { PrivateKeyTypeList } from '@/constants'
|
import AutoCertStepOne from '@/views/domain/cert/components/AutoCertStepOne.vue'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
issued: [void]
|
issued: [void]
|
||||||
|
@ -12,10 +11,9 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const step = ref(0)
|
const step = ref(0)
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const data = ref({}) as Ref<Cert>
|
const data = ref({}) as Ref<AutoCertOptions>
|
||||||
const issuing_cert = ref(false)
|
const issuing_cert = ref(false)
|
||||||
|
|
||||||
provide('data', data)
|
|
||||||
provide('issuing_cert', issuing_cert)
|
provide('issuing_cert', issuing_cert)
|
||||||
function open() {
|
function open() {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
@ -23,7 +21,7 @@ function open() {
|
||||||
data.value = {
|
data.value = {
|
||||||
challenge_method: 'dns01',
|
challenge_method: 'dns01',
|
||||||
key_type: '2048',
|
key_type: '2048',
|
||||||
} as Cert
|
} as AutoCertOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
@ -66,8 +64,6 @@ const issueCert = () => {
|
||||||
force-render
|
force-render
|
||||||
>
|
>
|
||||||
<template v-if="step === 0">
|
<template v-if="step === 0">
|
||||||
<DNSChallenge />
|
|
||||||
|
|
||||||
<AForm layout="vertical">
|
<AForm layout="vertical">
|
||||||
<AFormItem :label="$gettext('Domain')">
|
<AFormItem :label="$gettext('Domain')">
|
||||||
<AInput
|
<AInput
|
||||||
|
@ -75,19 +71,15 @@ const issueCert = () => {
|
||||||
addon-before="*."
|
addon-before="*."
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
||||||
<AFormItem :label="$gettext('Key Type')">
|
|
||||||
<ASelect v-model:value="data.key_type">
|
|
||||||
<ASelectOption
|
|
||||||
v-for="t in PrivateKeyTypeList"
|
|
||||||
:key="t.key"
|
|
||||||
:value="t.key"
|
|
||||||
>
|
|
||||||
{{ t.name }}
|
|
||||||
</ASelectOption>
|
|
||||||
</ASelect>
|
|
||||||
</AFormItem>
|
|
||||||
</AForm>
|
</AForm>
|
||||||
|
|
||||||
|
<AutoCertStepOne
|
||||||
|
v-model:options="data"
|
||||||
|
style="max-width: 600px"
|
||||||
|
hide-note
|
||||||
|
force-dns-challenge
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="step === 0"
|
v-if="step === 0"
|
||||||
class="flex justify-end"
|
class="flex justify-end"
|
||||||
|
@ -106,6 +98,7 @@ const issueCert = () => {
|
||||||
ref="refObtainCertLive"
|
ref="refObtainCertLive"
|
||||||
v-model:modal-closable="modalClosable"
|
v-model:modal-closable="modalClosable"
|
||||||
v-model:modal-visible="modalVisible"
|
v-model:modal-visible="modalVisible"
|
||||||
|
:options="data"
|
||||||
/>
|
/>
|
||||||
</AModal>
|
</AModal>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,11 +2,9 @@
|
||||||
import ObtainCert from '@/views/domain/cert/components/ObtainCert.vue'
|
import ObtainCert from '@/views/domain/cert/components/ObtainCert.vue'
|
||||||
import type { NgxDirective } from '@/api/ngx'
|
import type { NgxDirective } from '@/api/ngx'
|
||||||
|
|
||||||
export interface Props {
|
defineProps<{
|
||||||
configName: string
|
configName: string
|
||||||
}
|
}>()
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
const issuing_cert = ref(false)
|
const issuing_cert = ref(false)
|
||||||
const obtain_cert = ref()
|
const obtain_cert = ref()
|
||||||
|
@ -16,18 +14,16 @@ const enabled = defineModel<boolean>('enabled', {
|
||||||
default: () => false,
|
default: () => false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const no_server_name = computed(() => {
|
const noServerName = computed(() => {
|
||||||
if (!directivesMap.value.server_name)
|
if (!directivesMap.value.server_name)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
return directivesMap.value.server_name.length === 0
|
return directivesMap.value.server_name.length === 0
|
||||||
})
|
})
|
||||||
|
|
||||||
provide('no_server_name', no_server_name)
|
|
||||||
provide('props', props)
|
|
||||||
provide('issuing_cert', issuing_cert)
|
provide('issuing_cert', issuing_cert)
|
||||||
|
|
||||||
watch(no_server_name, () => {
|
watch(noServerName, () => {
|
||||||
enabled.value = false
|
enabled.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -45,6 +41,8 @@ async function onchange() {
|
||||||
<ObtainCert
|
<ObtainCert
|
||||||
ref="obtain_cert"
|
ref="obtain_cert"
|
||||||
:key="update"
|
:key="update"
|
||||||
|
:no-server-name="noServerName"
|
||||||
|
:config-name="configName"
|
||||||
@update:auto_cert="r => enabled = r"
|
@update:auto_cert="r => enabled = r"
|
||||||
/>
|
/>
|
||||||
<div class="issue-cert">
|
<div class="issue-cert">
|
||||||
|
@ -52,7 +50,7 @@ async function onchange() {
|
||||||
<ASwitch
|
<ASwitch
|
||||||
:loading="issuing_cert"
|
:loading="issuing_cert"
|
||||||
:checked="enabled"
|
:checked="enabled"
|
||||||
:disabled="no_server_name"
|
:disabled="noServerName"
|
||||||
@change="onchange"
|
@change="onchange"
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
|
@ -1,43 +1,38 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
import { $gettext } from '../../../../gettext'
|
||||||
import type { DnsChallenge } from '@/api/auto_cert'
|
import type { AutoCertOptions } from '@/api/auto_cert'
|
||||||
import DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
|
import DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
|
||||||
import type { Cert } from '@/api/cert'
|
|
||||||
import ACMEUserSelector from '@/views/certificate/ACMEUserSelector.vue'
|
import ACMEUserSelector from '@/views/certificate/ACMEUserSelector.vue'
|
||||||
import { PrivateKeyTypeList } from '@/constants'
|
import { PrivateKeyTypeList } from '@/constants'
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
hideNote?: boolean
|
hideNote?: boolean
|
||||||
|
forceDnsChallenge?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const no_server_name = inject('no_server_name')
|
const data = defineModel<AutoCertOptions>('options', {
|
||||||
|
default: () => {
|
||||||
// Provide by ObtainCert.vue
|
return {}
|
||||||
const data = inject('data') as Ref<DnsChallenge & Cert>
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!data.value.key_type)
|
if (!data.value.key_type)
|
||||||
data.value.key_type = '2048'
|
data.value.key_type = '2048'
|
||||||
|
|
||||||
|
if (props.forceDnsChallenge)
|
||||||
|
data.value.challenge_method = 'dns01'
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.forceDnsChallenge, v => {
|
||||||
|
if (v)
|
||||||
|
data.value.challenge_method = 'dns01'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template v-if="no_server_name">
|
|
||||||
<AAlert
|
|
||||||
:message="$gettext('Warning')"
|
|
||||||
type="warning"
|
|
||||||
show-icon
|
|
||||||
>
|
|
||||||
<template #description>
|
|
||||||
<span v-if="no_server_name">
|
|
||||||
{{ $gettext('server_name parameter is required') }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AAlert>
|
|
||||||
<br>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<AAlert
|
<AAlert
|
||||||
v-if="!hideNote"
|
v-if="!hideNote"
|
||||||
type="info"
|
type="info"
|
||||||
|
@ -52,8 +47,8 @@ onMounted(() => {
|
||||||
+ 'multiple domains.') }}
|
+ 'multiple domains.') }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ $gettext('The certificate for the domain will be checked 5 minutes, '
|
{{ $gettext('The certificate for the domain will be checked 30 minutes, '
|
||||||
+ 'and will be renewed if it has been more than 1 week since it was last issued.') }}
|
+ 'and will be renewed if it has been more than 1 week or the period you set in settings since it was last issued.') }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="data.challenge_method === 'http01'">
|
<p v-if="data.challenge_method === 'http01'">
|
||||||
{{ $gettext('Make sure you have configured a reverse proxy for .well-known '
|
{{ $gettext('Make sure you have configured a reverse proxy for .well-known '
|
||||||
|
@ -67,7 +62,10 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</AAlert>
|
</AAlert>
|
||||||
<AForm layout="vertical">
|
<AForm layout="vertical">
|
||||||
<AFormItem :label="$gettext('Challenge Method')">
|
<AFormItem
|
||||||
|
v-if="!forceDnsChallenge"
|
||||||
|
:label="$gettext('Challenge Method')"
|
||||||
|
>
|
||||||
<ASelect v-model:value="data.challenge_method">
|
<ASelect v-model:value="data.challenge_method">
|
||||||
<ASelectOption value="http01">
|
<ASelectOption value="http01">
|
||||||
{{ $gettext('HTTP01') }}
|
{{ $gettext('HTTP01') }}
|
||||||
|
@ -89,8 +87,32 @@ onMounted(() => {
|
||||||
</ASelect>
|
</ASelect>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
</AForm>
|
</AForm>
|
||||||
<ACMEUserSelector />
|
<ACMEUserSelector v-model:options="data" />
|
||||||
<DNSChallenge v-if="data.challenge_method === 'dns01'" />
|
<DNSChallenge
|
||||||
|
v-if="data.challenge_method === 'dns01'"
|
||||||
|
v-model:options="data"
|
||||||
|
/>
|
||||||
|
<AForm layout="vertical">
|
||||||
|
<AFormItem :label="$gettext('OCSP Must Staple')">
|
||||||
|
<template #help>
|
||||||
|
<p>
|
||||||
|
{{ $gettext('Do not enable this option unless you are sure that you need it.') }}
|
||||||
|
{{ $gettext('OCSP Must Staple may cause errors for some users on first access using Firefox.') }}
|
||||||
|
<a href="https://github.com/0xJacky/nginx-ui/issues/322">#322</a>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<ASwitch v-model:checked="data.must_staple" />
|
||||||
|
</AFormItem>
|
||||||
|
<AFormItem :label="$gettext('Lego disable CNAME Support')">
|
||||||
|
<template #help>
|
||||||
|
<p>
|
||||||
|
{{ $gettext('If your domain has CNAME records and you cannot obtain certificates, '
|
||||||
|
+ 'you need to enable this option.') }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<ASwitch v-model:checked="data.lego_disable_cname_support" />
|
||||||
|
</AFormItem>
|
||||||
|
</AForm>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectProps } from 'ant-design-vue'
|
import type { SelectProps } from 'ant-design-vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { SelectValue } from 'ant-design-vue/es/select'
|
import type { AutoCertOptions, DNSProvider } from '@/api/auto_cert'
|
||||||
import type { DNSProvider } from '@/api/auto_cert'
|
|
||||||
import auto_cert from '@/api/auto_cert'
|
import auto_cert from '@/api/auto_cert'
|
||||||
import dns_credential from '@/api/dns_credential'
|
import dns_credential from '@/api/dns_credential'
|
||||||
|
|
||||||
const providers = ref([]) as Ref<DNSProvider[]>
|
const providers = ref([]) as Ref<DNSProvider[]>
|
||||||
const credentials = ref<SelectProps['options']>([])
|
const credentials = ref<SelectProps['options']>([])
|
||||||
|
|
||||||
// This data is provided by the Top StdCurd component,
|
const data = defineModel<AutoCertOptions>('options', {
|
||||||
// is the object that you are trying to modify it
|
default: () => {
|
||||||
// we externalize the dns_credential_id to the parent component,
|
return {}
|
||||||
// this is used to tell the backend which dns_credential to use
|
},
|
||||||
const data = inject('data') as Ref<DNSProvider & { dns_credential_id: SelectValue }>
|
required: true,
|
||||||
|
})
|
||||||
|
|
||||||
const code = computed(() => {
|
const code = computed(() => {
|
||||||
return data.value.code
|
return data.value.code
|
||||||
|
@ -95,7 +95,7 @@ const filterOption = (input: string, option: { label: string }) => {
|
||||||
<ASelect
|
<ASelect
|
||||||
v-model:value="provider_idx"
|
v-model:value="provider_idx"
|
||||||
show-search
|
show-search
|
||||||
:options="options"
|
:options
|
||||||
:filter-option="filterOption"
|
:filter-option="filterOption"
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
@ -105,7 +105,7 @@ const filterOption = (input: string, option: { label: string }) => {
|
||||||
:rules="[{ required: true }]"
|
:rules="[{ required: true }]"
|
||||||
>
|
>
|
||||||
<ASelect
|
<ASelect
|
||||||
v-model:value="data.dns_credential_id"
|
v-model:value="data.dns_credential_id as any"
|
||||||
:options="credentials"
|
:options="credentials"
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
|
@ -4,12 +4,16 @@ import type { ComputedRef, Ref } from 'vue'
|
||||||
import domain from '@/api/domain'
|
import domain from '@/api/domain'
|
||||||
import AutoCertStepOne from '@/views/domain/cert/components/AutoCertStepOne.vue'
|
import AutoCertStepOne from '@/views/domain/cert/components/AutoCertStepOne.vue'
|
||||||
import type { NgxConfig, NgxDirective } from '@/api/ngx'
|
import type { NgxConfig, NgxDirective } from '@/api/ngx'
|
||||||
import type { Props } from '@/views/domain/cert/IssueCert.vue'
|
import type { AutoCertOptions } from '@/api/auto_cert'
|
||||||
import type { DnsChallenge } from '@/api/auto_cert'
|
|
||||||
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
import ObtainCertLive from '@/views/domain/cert/components/ObtainCertLive.vue'
|
||||||
import type { CertificateResult } from '@/api/cert'
|
import type { CertificateResult } from '@/api/cert'
|
||||||
import type { PrivateKeyType } from '@/constants'
|
import type { PrivateKeyType } from '@/constants'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
configName: string
|
||||||
|
noServerName?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update:auto_cert'])
|
const emit = defineEmits(['update:auto_cert'])
|
||||||
|
|
||||||
const modalVisible = ref(false)
|
const modalVisible = ref(false)
|
||||||
|
@ -26,15 +30,11 @@ const data = ref({
|
||||||
credentials: {},
|
credentials: {},
|
||||||
additional: {},
|
additional: {},
|
||||||
},
|
},
|
||||||
}) as Ref<DnsChallenge>
|
}) as Ref<AutoCertOptions>
|
||||||
|
|
||||||
const modalClosable = ref(true)
|
const modalClosable = ref(true)
|
||||||
|
|
||||||
provide('data', data)
|
|
||||||
|
|
||||||
const save_config = inject('save_config') as () => Promise<void>
|
const save_config = inject('save_config') as () => Promise<void>
|
||||||
const no_server_name = inject('no_server_name') as Ref<boolean>
|
|
||||||
const props = inject('props') as Props
|
|
||||||
const issuing_cert = inject('issuing_cert') as Ref<boolean>
|
const issuing_cert = inject('issuing_cert') as Ref<boolean>
|
||||||
const ngx_config = inject('ngx_config') as NgxConfig
|
const ngx_config = inject('ngx_config') as NgxConfig
|
||||||
const current_server_directives = inject('current_server_directives') as ComputedRef<NgxDirective[]>
|
const current_server_directives = inject('current_server_directives') as ComputedRef<NgxDirective[]>
|
||||||
|
@ -61,8 +61,8 @@ function change_auto_cert(status: boolean, key_type?: PrivateKeyType) {
|
||||||
if (status) {
|
if (status) {
|
||||||
domain.add_auto_cert(props.configName, {
|
domain.add_auto_cert(props.configName, {
|
||||||
domains: name.value.trim().split(' '),
|
domains: name.value.trim().split(' '),
|
||||||
challenge_method: data.value.challenge_method,
|
challenge_method: data.value.challenge_method!,
|
||||||
dns_credential_id: data.value.dns_credential_id,
|
dns_credential_id: data.value.dns_credential_id!,
|
||||||
key_type: key_type!,
|
key_type: key_type!,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
message.success($gettext('Auto-renewal enabled for %{name}', { name: name.value }))
|
message.success($gettext('Auto-renewal enabled for %{name}', { name: name.value }))
|
||||||
|
@ -98,7 +98,7 @@ function job() {
|
||||||
modalClosable.value = false
|
modalClosable.value = false
|
||||||
issuing_cert.value = true
|
issuing_cert.value = true
|
||||||
|
|
||||||
if (no_server_name.value) {
|
if (props.noServerName) {
|
||||||
message.error($gettext('server_name not found in directives'))
|
message.error($gettext('server_name not found in directives'))
|
||||||
issuing_cert.value = false
|
issuing_cert.value = false
|
||||||
|
|
||||||
|
@ -183,13 +183,17 @@ function next() {
|
||||||
force-render
|
force-render
|
||||||
>
|
>
|
||||||
<template v-if="step === 1">
|
<template v-if="step === 1">
|
||||||
<AutoCertStepOne />
|
<AutoCertStepOne
|
||||||
|
v-model:options="data"
|
||||||
|
:no-server-name="noServerName"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="step === 2">
|
<template v-else-if="step === 2">
|
||||||
<ObtainCertLive
|
<ObtainCertLive
|
||||||
ref="refObtainCertLive"
|
ref="refObtainCertLive"
|
||||||
v-model:modal-closable="modalClosable"
|
v-model:modal-closable="modalClosable"
|
||||||
v-model:modal-visible="modalVisible"
|
v-model:modal-visible="modalVisible"
|
||||||
|
:options="data"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,40 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import websocket from '@/lib/websocket'
|
import websocket from '@/lib/websocket'
|
||||||
import type { DnsChallenge } from '@/api/auto_cert'
|
|
||||||
import Error from '@/views/other/Error.vue'
|
|
||||||
import type { CertificateResult } from '@/api/cert'
|
import type { CertificateResult } from '@/api/cert'
|
||||||
|
import type { AutoCertOptions } from '@/api/auto_cert'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modalClosable: boolean
|
options: AutoCertOptions
|
||||||
modalVisible: boolean
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const modalVisible = defineModel<boolean>('modalVisible')
|
||||||
'update:modalClosable': [value: boolean]
|
const modalClosable = defineModel<boolean>('modalClosable')
|
||||||
'update:modalVisible': [value: boolean]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const modalClosable = computed({
|
|
||||||
get() {
|
|
||||||
return props.modalClosable
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
emit('update:modalClosable', value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const modalVisible = computed({
|
|
||||||
get() {
|
|
||||||
return props.modalVisible
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
emit('update:modalVisible', value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const issuing_cert = inject('issuing_cert') as Ref<boolean>
|
const issuing_cert = inject('issuing_cert') as Ref<boolean>
|
||||||
const data = inject('data') as Ref<DnsChallenge>
|
|
||||||
|
|
||||||
const progressStrokeColor = {
|
const progressStrokeColor = {
|
||||||
from: '#108ee9',
|
from: '#108ee9',
|
||||||
|
@ -71,8 +48,8 @@ const issue_cert = async (config_name: string, server_name: string[], key_type:
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
server_name,
|
server_name,
|
||||||
|
...props.options,
|
||||||
key_type,
|
key_type,
|
||||||
...data.value,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +91,7 @@ const issue_cert = async (config_name: string, server_name: string[], key_type:
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
progressStatus.value = 'exception'
|
progressStatus.value = 'exception'
|
||||||
reject(new Error($gettext('Fail to obtain certificate')))
|
reject($gettext('Fail to obtain certificate'))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,12 +71,14 @@ func autoCert(certModel *model.Cert) {
|
||||||
|
|
||||||
// support SAN certification
|
// support SAN certification
|
||||||
payload := &ConfigPayload{
|
payload := &ConfigPayload{
|
||||||
CertID: certModel.ID,
|
CertID: certModel.ID,
|
||||||
ServerName: certModel.Domains,
|
ServerName: certModel.Domains,
|
||||||
ChallengeMethod: certModel.ChallengeMethod,
|
ChallengeMethod: certModel.ChallengeMethod,
|
||||||
DNSCredentialID: certModel.DnsCredentialID,
|
DNSCredentialID: certModel.DnsCredentialID,
|
||||||
KeyType: certModel.GetKeyType(),
|
KeyType: certModel.GetKeyType(),
|
||||||
NotBefore: certInfo.NotBefore,
|
NotBefore: certInfo.NotBefore,
|
||||||
|
MustStaple: certModel.MustStaple,
|
||||||
|
LegoDisableCNAMESupport: certModel.LegoDisableCNAMESupport,
|
||||||
}
|
}
|
||||||
|
|
||||||
if certModel.Resource != nil {
|
if certModel.Resource != nil {
|
||||||
|
|
|
@ -130,7 +130,6 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
||||||
errChan <- errors.Wrap(err, "environment configuration is empty")
|
errChan <- errors.Wrap(err, "environment configuration is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -138,6 +137,18 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix #407
|
||||||
|
if payload.LegoDisableCNAMESupport {
|
||||||
|
err = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true")
|
||||||
|
if err != nil {
|
||||||
|
errChan <- errors.Wrap(err, "set env flag to disable lego CNAME support error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.Unsetenv("LEGO_DISABLE_CNAME_SUPPORT")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if time.Now().Sub(payload.NotBefore).Hours()/24 <= 21 &&
|
if time.Now().Sub(payload.NotBefore).Hours()/24 <= 21 &&
|
||||||
payload.Resource != nil && payload.Resource.Certificate != nil {
|
payload.Resource != nil && payload.Resource.Certificate != nil {
|
||||||
renew(payload, client, l, errChan)
|
renew(payload, client, l, errChan)
|
||||||
|
|
|
@ -10,8 +10,9 @@ import (
|
||||||
|
|
||||||
func obtain(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
func obtain(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
Domains: payload.ServerName,
|
Domains: payload.ServerName,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
|
MustStaple: payload.MustStaple,
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Println("[INFO] [Nginx UI] Obtaining certificate")
|
l.Println("[INFO] [Nginx UI] Obtaining certificate")
|
||||||
|
|
|
@ -16,17 +16,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigPayload struct {
|
type ConfigPayload struct {
|
||||||
CertID int `json:"cert_id"`
|
CertID int `json:"cert_id"`
|
||||||
ServerName []string `json:"server_name"`
|
ServerName []string `json:"server_name"`
|
||||||
ChallengeMethod string `json:"challenge_method"`
|
ChallengeMethod string `json:"challenge_method"`
|
||||||
DNSCredentialID int `json:"dns_credential_id"`
|
DNSCredentialID int `json:"dns_credential_id"`
|
||||||
ACMEUserID int `json:"acme_user_id"`
|
ACMEUserID int `json:"acme_user_id"`
|
||||||
KeyType certcrypto.KeyType `json:"key_type"`
|
KeyType certcrypto.KeyType `json:"key_type"`
|
||||||
Resource *model.CertificateResource `json:"resource,omitempty"`
|
Resource *model.CertificateResource `json:"resource,omitempty"`
|
||||||
NotBefore time.Time `json:"-"`
|
MustStaple bool `json:"must_staple"`
|
||||||
CertificateDir string `json:"-"`
|
LegoDisableCNAMESupport bool `json:"lego_disable_cname_support"`
|
||||||
SSLCertificatePath string `json:"-"`
|
NotBefore time.Time `json:"-"`
|
||||||
SSLCertificateKeyPath string `json:"-"`
|
CertificateDir string `json:"-"`
|
||||||
|
SSLCertificatePath string `json:"-"`
|
||||||
|
SSLCertificateKeyPath string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigPayload) GetACMEUser() (user *model.AcmeUser, err error) {
|
func (c *ConfigPayload) GetACMEUser() (user *model.AcmeUser, err error) {
|
||||||
|
|
|
@ -1,38 +1,39 @@
|
||||||
package cert
|
package cert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/model"
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func renew(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
func renew(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
|
||||||
if payload.Resource == nil {
|
if payload.Resource == nil {
|
||||||
errChan <- errors.New("resource is nil")
|
errChan <- errors.New("resource is nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
options := &certificate.RenewOptions{
|
options := &certificate.RenewOptions{
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
MustStaple: payload.MustStaple,
|
||||||
|
}
|
||||||
|
|
||||||
cert, err := client.Certificate.RenewWithOptions(payload.Resource.GetResource(), options)
|
cert, err := client.Certificate.RenewWithOptions(payload.Resource.GetResource(), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- errors.Wrap(err, "renew cert error")
|
errChan <- errors.Wrap(err, "renew cert error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.Resource = &model.CertificateResource{
|
payload.Resource = &model.CertificateResource{
|
||||||
Resource: cert,
|
Resource: cert,
|
||||||
PrivateKey: cert.PrivateKey,
|
PrivateKey: cert.PrivateKey,
|
||||||
Certificate: cert.Certificate,
|
Certificate: cert.Certificate,
|
||||||
IssuerCertificate: cert.IssuerCertificate,
|
IssuerCertificate: cert.IssuerCertificate,
|
||||||
CSR: cert.CSR,
|
CSR: cert.CSR,
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.WriteFile(l, errChan)
|
payload.WriteFile(l, errChan)
|
||||||
|
|
||||||
l.Println("[INFO] [Nginx UI] Certificate renewed successfully")
|
l.Println("[INFO] [Nginx UI] Certificate renewed successfully")
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,21 +30,23 @@ type CertificateResource struct {
|
||||||
|
|
||||||
type Cert struct {
|
type Cert struct {
|
||||||
Model
|
Model
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Domains pq.StringArray `json:"domains" gorm:"type:text[]"`
|
Domains pq.StringArray `json:"domains" gorm:"type:text[]"`
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
SSLCertificatePath string `json:"ssl_certificate_path"`
|
SSLCertificatePath string `json:"ssl_certificate_path"`
|
||||||
SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
|
SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
|
||||||
AutoCert int `json:"auto_cert"`
|
AutoCert int `json:"auto_cert"`
|
||||||
ChallengeMethod string `json:"challenge_method"`
|
ChallengeMethod string `json:"challenge_method"`
|
||||||
DnsCredentialID int `json:"dns_credential_id"`
|
DnsCredentialID int `json:"dns_credential_id"`
|
||||||
DnsCredential *DnsCredential `json:"dns_credential,omitempty"`
|
DnsCredential *DnsCredential `json:"dns_credential,omitempty"`
|
||||||
ACMEUserID int `json:"acme_user_id"`
|
ACMEUserID int `json:"acme_user_id"`
|
||||||
ACMEUser *AcmeUser `json:"acme_user,omitempty"`
|
ACMEUser *AcmeUser `json:"acme_user,omitempty"`
|
||||||
KeyType certcrypto.KeyType `json:"key_type"`
|
KeyType certcrypto.KeyType `json:"key_type"`
|
||||||
Log string `json:"log"`
|
Log string `json:"log"`
|
||||||
Resource *CertificateResource `json:"-" gorm:"serializer:json"`
|
Resource *CertificateResource `json:"-" gorm:"serializer:json"`
|
||||||
SyncNodeIds []int `json:"sync_node_ids" gorm:"serializer:json"`
|
SyncNodeIds []int `json:"sync_node_ids" gorm:"serializer:json"`
|
||||||
|
MustStaple bool `json:"must_staple"`
|
||||||
|
LegoDisableCNAMESupport bool `json:"lego_disable_cname_support"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FirstCert(confName string) (c Cert, err error) {
|
func FirstCert(confName string) (c Cert, err error) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue